垃圾回收与算法

一、如何确定垃圾

Java采用引用计数法和可达性分析来确定对象是否应该被回收。引用计数法对于循环引用问题无法解决,可达性分析通过根搜索算法来实现,在一个对象到任何GC Roots都没有引用链相连时,说明其已经死亡。根搜索算法主要针对栈中的引用、方法区的静态引用和JNI(Java Native Interface )中的引用展开分析,如图所示:

1、引用计数

在为对象添加一个引用时,引用计数值加1,在为对象删除一个引用时,引用计数值减1,如果一个对象的引用计数为0,则表示该对象没有被引用,可以被回收。

引用计数法对于循环引用问题无法解决。循环引用是指两个对象相互引用,导致他们的引用计数一直为1,因而无法被回收。

2、可达性分析

为了解决循环引用问题,JVM还采用了可达性分析来判断对象是否可以被回收。可达性分析首先定义一些GC Roots节点,然后以这些GC Roots节点作为起点向下搜索,如果GC Roots和一个对象之间没有任何可达路径,则称该对象是不可达的,可以被回收。

二、垃圾回收算法

常用的垃圾回收算法:标记清除、复制、标记整理。如图所示:

1、标记清除算法

标记清除算法的过程分为标记和清除两个阶段。在标记阶段标记所有需要回收的对象,在清除阶段清除被标记的对象并释放其内存空间。如图所示:

由于标记清除算法在清理对象所占用的内存空间后并没有重新整理可用的内存空间,因此如果内存中可被回收的小对象居多,则会引起内存碎片化问题,继而引起大对象无法获得连续可用的内存空间。

2、复制算法

复制算法是为了解决标记清除算法内存碎片化问题而设计的。复制算法首先将内存划分为两块大小相等的内存区域,即区域1和区域2,新生成的对象都被存储在区域1中,在区域1的空间存储满后会对区域1进行一次扫描,并将活着的对象复制到区域2中,当区域1整体扫描完之后,所有活着的对象都已经被复制到区域2中了,所以直接对区域1删除即可,删除之后将区域1和区域2的指向对换即可。

复制算法的内存清理效率高且易于实现,但由于同一时刻只有一个内存区域可用,即可用的内存空间被压缩到原来的一半,因此存在大量的内存浪费。同时,在系统中有大量长时间存活的对象时,这些对象将在内存区域1和区域2之间来回复制而影响系统的运行效率。

3、标记整理算法

标记整理算法结合了标记清除和复制算法的优点,其标记阶段和标记清除算法的标记阶段相同,在标记完成后将存活的对象移动到内存的另一端,然后清除该端的对象并释放内存。如图所示:

三、分代收集

无论是标记清除算法、复制算法、标记整理算法,都无法对所有类型(长生命周期、短生命周期、大对象、小对象)的对象进行垃圾回收。因此,针对不同的对象类型,JVM采用不同的垃圾回收算法,该算法被称为分代收集算法。

分代收集算法根据对象的不同类型将内存划分为不同的区域,JVM将堆内存划分为新生代和老年代。新生代主要存放新生成的对象,其特点是对象数量多但生命周期短,在每次进行垃圾回收时都有大量的对象被回收;老年代主要存放大对象和生命周期长的对象,可回收的对象相对较少。因此,JVM根据不同的区域对象的特点选择了不同的算法。

目前,大多数JVM在新生代都采用了复制算法,因为在新生代中每次进行垃圾回收时都有大量的对象被回收,需要复制的对象(存活的对象)较少,不存在大量的对象在内存中被来回复制的问题,因此采用复制算法能安全、高效地回收新生代大量的短生命周期的对象并释放内存。

JVM将新生代进一步划分为一块较大的Eden区和两块较小的Servivor区(ServivorFrom区和ServivorTo区,后面称ServivorFrom区为From区,称ServivorTo区为To区),Eden区、From区和To区比例为8:1:1。JVM在运行过程中主要使用Eden区和From区,进行垃圾回收时会将在Eden区和From区中存活的对象复制到To区,然后清理Eden区和From区的内存空间。

老年代主要存储生命周期较长的对象和大对象,因此每次只有少量可被回收的对象被回收,所以老年代采用标记清除算法。

JVM内存中的对象主要被分配到新生代的Eden区和From区,在少数情况下会被直接分配到老年代。在新生代的Eden区和From区内存空间不足时会触发一次GC,在Eden区和From区中存活的对象会被复制到To区,然后Eden区和From区被清理,清理完成后将From区和To区互换。如果此时在To区无法找到连续的内存空间存储某个对象,则将这个对象直接存储到老年代。若Servivor区的对象经过一次GC后仍然存活,则其年龄加1.在默认情况下,对象在年龄达到15时,将被移到老年代。

 

posted @ 2020-10-09 17:13  西北-孤狼  阅读(219)  评论(0)    收藏  举报