1,判断对象是否回收

1.1、引用计数算法
  
   对象中添加一个引用计数器,如果引用计数器为0则表示没有其它地方在引用它。如果有一个地方引用就+1,引用失效时就-1。实际上在大部分Java虚拟机中并没有采用这种算法,因为它会带来一个致命的问题——对象循环引用。对象A指向B,对象B反过来指向A,此时它们的引用计数器都不为0,但它们俩实际上已经没有意义因为没有任何地方指向它们。如下所示:在最后,object1和object2相互引用,它们的内存块都不能再被访问到了,但他们的引用计数都不为0,这就会使他们永远不会被清除。

1.2、可达性分析算法
  
   算法基本思路:通过一系列"GC Roots"对象作为起始点,开始向下搜索, 搜索所走过和路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(从GC Roots到这个对象不可达),则证明该对象是不可用的。
   会被认为是GC Roots的对象:
    被启动类(bootstrap加载器)加载的类和创建的对象;
    jvm运行时方法区类静态变量(static)引用的对象;
    jvm运行时方法去常量池引用的对象;
    jvm当前运行线程中的虚拟机栈变量表引用的对象;
    本地方法栈中(jni)引用的对象;
   由于这种算法即使存在互相引用的对象,但如果这两个对象无法访问到根对象,还是会被回收,jvm在确定是否回收的对象的时候采用的是root搜索算法来实现。在可达性分析算法中,我们说的引用这里一般都指定的是强引用关系。下篇会介绍java中四种引用关系。
   
2,垃圾回收算法
   1.标记-清除算法
    该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
    执行过程如下图所示:
    jvm学习(二)之垃圾回收算法-LMLPHP
    2.复制算法
    复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
    执行过程如下图所示:
    jvm学习(二)之垃圾回收算法-LMLPHP
    3.标记整理算法
    标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法可以避免100%对象存活的极端状况,因此老年代不能直接使用该算法。
    执行过程如下图所示:
    jvm学习(二)之垃圾回收算法-LMLPHP
    4.分代收集算法
    分代收集不是一种新算法,只是根据对象存活周期不同将内存划分为几块。一般是新生代和老年代。这样就可以根据每个年代的特点采用最适当的收集算法。新生代中,每次垃圾回收时都有大量垃圾需要被回收,可以选复制算法。老年代每次垃圾回收时只有少量对象需要被回收,可以采用“标记清理”或者“标记整理”算法
    目前大部分垃圾收集器对于新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间(一般为8:1:1),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。而由于老年代的特点是每次回收都只回收少量对象,一般使用的是标记整理算法。
    如下图:
    jvm学习(二)之垃圾回收算法-LMLPHP
   对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。如果To Space无法足够存储某个对象,则将这个对象存储到老生代。在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达一定的对象会被移到老生代中。
   另外,永生代(方法区)。它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类,垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因,Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区。
   为什么JVM新生代中有两个survivor呢,因为将eden区的存活对象复制到survivor区时,必须保证survivor区是空的,如果survivor区中已有上次复制的存活对象时,这次再复制的对象肯定和上次的内存地址是不连续的,会产生内存碎片,浪费survivor空间。
   碎片化带来的风险是极大的,严重影响JAVA程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果就是,堆中没有足够大的连续内存空间,接下去如果程序需要给一个内存需求很大的对象分配内存,就会内存溢出了。
   而如果有两个survivor区,第一次GC后,把eden区和survivor0区一起复制到survivor1区,然后清空survivor0和eden区,此时survivor1非空,survivor0和eden区为空,下一次GC时把survivor0和survivor1交换,这样就能保证向survivor区复制时始终都有一个survivor区是空的,也就能保证新对象能始终在eden区出生了。

10-04 11:45