Java引入了垃圾回收机制,令C++程序员最头疼的内存管理问题迎刃而解。Java程序员可以将更多的精力放到业务逻辑上而不是内存管理工作上,大大的提高了开发效率。

内存管理

Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放。

  • 对象空间的分配:使用new关键字创建对象即可
  • 对象空间的释放:将对象赋值null即可。垃圾回收器将负责回收所有”不可达”对象的内存空间。

垃圾回收过程

任何一种垃圾回收算法一般要做两件基本事情:

  1. 发现无用的对象,一般采用可达性方法判断是否为无用的对象
  2. 回收无用对象占用的内存空间

垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的就是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象,并进行清除和整理。

垃圾回收相关算法

引用计数法

  1. 给每一个对象分配一个引用计数器
  2. 每当有新的引用指向这个对象的时候,该引用计数器值+1
  3. 而如果有引用结束了对该对象的引用,则计数器值-1.
  4. 当垃圾回收线程检测到计数器值为0时回收期则会回收对象所使用的内存。
public class Student {
    String name;
    Student friend;
     
    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student();
         
        s1.friend = s2;
        s2.friend = s1;        
        s1 = null;
        s2 = null;
    }
}

s1和s2互相引用对方,导致他们引用计数不为0,但是实际已经无用,但无法被识别。

复制算法

  1. 将内存分为大小相等的两个部分,每次只使用其中的一个部分,等这部分内存空间消耗完时,检查该空间里的所有对象。
  2. 如果对象还被需要或者还有活性,则将这类对象复制到另一部分内存中,而不再需要的对象则不进行复制。
  3. 当复制完成后,将之前被完全消耗的那部分空间全部清理。

因此,在复制算法中,每一次内存清理的目标是一半的内存空间,相比引用计数法更简单高效。但是可使用的内存则降为了原来的一般。可以理解为以空间损失换来时间上的优化。

标记清除与标记压缩算法

标记清除算法就将垃圾回收分为了两部分:标记(mark)和清除(sweep)。

标记:根据特定的算法判断内存中的对象是否可以回收,其中常见的标记方法有前面提到的引用计数算法或是可达性分析算法等。

清除or压缩:清除与压缩的区别在与是否把可回收的那部分内存压缩到一起,进行整体垃圾清理。Java曾使用过标记压缩算法来实现垃圾回收。

分代收集算法

根据内存中的对象存活周期,将内存划分为几块,Java的虚拟机中一般把内存划分为老年代和新生代。

  • 老年代:每次垃圾收集只有少量对象需要被回收。
  • 新生代:每次垃圾回收时都有大量的对象需要被回收。
  • 永久代:堆区之外,用来存储类、常量和方法等。

这样划分的目的是为了根据不同代的特点采用最合适的收集算法。

新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

而由于老年代的特点是每次回收都只回收少量对象,一般使用的是标记压缩算法。