垃圾收集
Java的一大特点就是可以进行自动垃圾回收处理。自动垃圾回收减轻了开发人员的工作量,但增加了系统的负担。
1、引用计数法
引用计数法是最经典也是最古老的一种垃圾收集方法。对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器+1,当引用失效后,引用计数器-1。只要对象A的引用计数器的值为0,则说明对象A已经不会再被使用,可以进行垃圾回收。
引用计数法的实现比较简单,只需要为美俄对象配备一个整形的计数器即可。但是引用计数器有一个严重的问题,即无法处理循环引用的情况,所以Java中没有使用引用计数法。
如图,A引用B,B引用A,A和B的引用均为1,但是除了A与B,没有其他任何对象引用A或B,则此时可认为A跟B应该进行垃圾回收。
2、标记-清除算法
标记-清除算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。
标记阶段:
通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。
清除阶段:
清除所有未被标记的垃圾对象。
标记-清除算法可能产生的最大问题就是空间碎片。
那么,怎么确定根节点呢?在Java中可以作为GC Roots的对象:
1、虚拟机栈(左上)的栈帧的局部变量表所引用的对象
2、本地方法栈(右中)的JNI所引用的对象
3、方法区(右下)的静态变量和常量所引用的对象
回收后的空间是不连续的,在给对象分配内存时,尤其是大对象,不连续的内存空间工作效率要低于连续的内存空间。
3、复制算法
复制算法相对高效于标记-清除算法。核心思想是:将原有的内存空间分成两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到另一块未使用的内存块,清除正在使用的内存块对象。
所以当内存中的存活对象越少,所需要复制的对象也越少,算法效率越高。复制后的内存空间为连续的内存空间,所以不会产生空间碎片。复制算法使系统内存折半。
Java的新生代串行垃圾回收器使用了复制算法。新生代又eden去,from区(s0),to区(s1)组成。from区和to区也成Survivor空间,即幸存者空间,用于存放未被回收的对象。from区和to区就使用到了复制算法。
复制算法比较适合用于新生代,因为新生代中的垃圾对象一般都会多余存活对象。
4、标记-压缩算法
标记-压缩算法是老年代的一种回收算法。它在标记-清除算法的基础上做了一些优化。标记-压缩法从根节点开始,对所有可达对象做标记,然后清理垃圾对象,接着将活着的对象压缩到内存一侧,避免碎片产生。
5、增量算法
增量算法的基本思想是,如果一次性将所有的垃圾进行处理。需要造成系统长时间的停顿,那么就可以将垃圾收集线程和应用程序线程交替进行。每次垃圾收集线程至收集一小片内存空间。接着切换到应用程序线程。以此反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收的过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾收集的总体成本上升,造成系统吞吐量下降。
6、分代
分代是将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收效率。
以HotSpot虚拟机为例,它将所有的新建对象都放入到年轻代的内存区间,年轻代的特点是对象存活率低,容易被回收。因此,年轻代可以选择效率较高的复制算法。当对象经过几次回收后依然存活,对象会被放入到老年代的内存区间,在老年代中,几乎所有的对象都是经过几次垃圾回收后依然存活的对象,因此,可以认为这些对象在一段时间,甚至在应用程序的整个生命周期中常驻内存。根据老年代的特点,可以选择标记-压缩算法以提高垃圾回收效率。
参考:
《Java程序性能优化 让你的Java程序更快、更稳定》