《深入理解Java虚拟机》学习笔记———对GC算法理解

《深入理解Java虚拟机》学习笔记———对GC算法理解

在商用Java虚拟机中,由于不同对象存活周期不同,根据存活周期可以分为新生代和老年代,对新生代和老年代采用不同的垃圾回收算法,叫做分代回收算法

对于新生代,每次回收时,都会有大部分对象死亡,只有少部分对象存活,所以采用复制算法,只需要花费少量的存活对象的复制成本。

对于老年代,因为存活率比较高,没有额外空间对它进行分配担保,所以采用标记-整理算法。

下面分别介绍下复制算法和标记-整理算法:

复制算法——将虚拟机内存分配为两个区域,每次只使用其中一块儿区域。当该区域空间用完后,将存活的对象移动到另一区域内,然后将已使用过的内存空间清理掉。HotSpot中,Eden和Survivor的比例分配为8:1。(注:复制算法的弊端是缩小了实际内存使用)
标记-整理算法——说到标记-整理算法,先要介绍另外一个算法——标记-清除算法标记-清除算法分为两个步骤,第一步要先标记出所有要回收的对象,第二步将标记出的对象统一回收。标记-清除算法有两个明显的不足是:1、效率问题,标记和清除这两个操作的效率都不高;2、清除以后内存空间会产生大量的不连续碎片,碎片太多程序运行过程中比较大的对象时,由于空间不足而导致再一次触发垃圾回收机制。所以在此基础上,提出了标记-整理算法。标记整理算法与标记清除算法不同的是,在标记出要回收的对象之后,不直接进行清除,而是将所有存活的对象向一端移动,然后清除边界意外的对象。

接下来介绍一下在标记-整理/清除算法中的标记过程

在jvm中,标记算法主要分为两种:引用计数法和可达性分析算法。下面分别介绍一下这两种标记算法:

引用计数法——在虚拟机堆中,对每个对象增加一个引用计数器,每当该对象被引用一次时,引用计数器加一,当引用时效后,引用计数器减一,当计数器为0时,该对象不能再被引用。引用计数器算法比较简单,效率也比较高,但有一个问题:

#

public class ReferenceCountingGC {

private Object INSTANCE;

private final int _1MB = 1024*1024;

private byte[]bigSize = new byte[_1MB*2];

public static void testGC(){
    ReferenceCountingGC r1 = new ReferenceCountingGC();
    ReferenceCountingGC r2 = new ReferenceCountingGC();

    r1.INSTANCE = r2;
    r2.INSTANCE = r1;

    r1=null;
    r2=null;
    System.gc();
}

public static void main(String[] args) {
    ReferenceCountingGC r1 = new ReferenceCountingGC();
    ReferenceCountingGC r2 = new ReferenceCountingGC();

    r1.INSTANCE = r2;
    r2.INSTANCE = r1;

    r1=null;
    r2=null;
    System.gc();
}

}

#

[F u l l G C(S y s t e m)[T e n u r e d:0 K->2 1 0 K(1 0 2 4 0 K),0.0 1 4 9 1 4 2 s e c s]4603K->210K(19456K),[Perm:2999K->
2999K(21248K)],0.0150007 secs][Times:user=0.01 sys=0.00,real=0.02 secs]
Heap
def new generation total 9216K,used 82K[0x00000000055e0000,0x0000000005fe0000,0x0000000005fe0000)
Eden space 8192K,1%used[0x00000000055e0000,0x00000000055f4850,0x0000000005de0000)
from space 1024K,0%used[0x0000000005de0000,0x0000000005de0000,0x0000000005ee0000)
to space 1024K,0%used[0x0000000005ee0000,0x0000000005ee0000,0x0000000005fe0000)
tenured generation total 10240K,used 210K[0x0000000005fe0000,0x00000000069e0000,0x00000000069e0000)
the space 10240K,2%used[0x0000000005fe0000,0x0000000006014a18,0x0000000006014c00,0x00000000069e0000)
compacting perm gen total 21248K,used 3016K[0x00000000069e0000,0x0000000007ea0000,0x000000000bde0000)
the space 21248K,14%used[0x00000000069e0000,0x0000000006cd2398,0x0000000006cd2400,0x0000000007ea0000)
No shared spaces configured.
}

这说明GC没有因为这两个对象相互引用而被回收.

接下来说一下可达性分析算法——通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径叫做引用链,当一个对象到GC Roots没有任何引用链相连,那么该对象被判定为可回收的对象。那么什么对象可以称作为“GC Roots”呢?在java语言中,GC Roots包含以下几类:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(一般说的是Native方法)引用的对象。

可达性分析算法中,即使没有关联到GC Roots的对象,也不一定会被回收。如果对象没有关联到GC Roots,则它们会被标记并进行一次筛选,首先会判断该对象有没有必要执行finalize()方法。没有必要执行finalize()方法的条件是对象没有覆盖finalize()方法或者finalize()方法被虚拟机调用过。有必要执行finalize()方法的对象会被放进F-Queue队列中,并在稍后由虚拟机自动建立的、低优先级的Finalizer线程去执行它(虚拟机会触发若finalize()方法但并不保证等待它运行结束)。若队列中的对象在finalize()中拯救了自己——只要和引用链上的任意一个对象建立了关联,比如把自己(this关键字)赋值给了某个类变量或者对象的成员变量,那么第二次标记时它将被移除“即将回收集合”。

介绍一下引用的概念。

在jdk1.2之前,引用的概念是指一个reference类型的数据中存储的数值是另一块内存的起始地址,那么就说这块内存代表着一个引用。但这个定义太过狭隘,对一些“食之无味,弃之可惜”的对象就显得无能为力。即有一部分对象在内存空间足够时希望能够保留、在内存空间不足时能够回收。因此,在jdk1.2之后,扩充了引用的概念,将引用分为了“强引用(strong reference)”、“软引用(soft reference)”、“弱引用(week reference)”和“虚引用(phantom reference)”四种。

强引用(strong reference)——指java代码中普遍存在的,类似于Object obj = new Object() 的对象,强引用的对象不会被垃圾回收器回收。
软引用(soft reference)——指的是一些有用但非必须的对象,当内存溢出时,jvm不会立即内存溢出,而是会对弱引用的对象再进行一次垃圾回收,如果回收后内存还是不足才会内存溢出。jdk1.2之后,提供SoftReference类来实现弱引用。
弱引用(week reference)——弱引用的对象只能活到下一次垃圾回收之前,当垃圾回收器回收时,不管内存是否足够,都会回收这部分对象。jdk1.2之后,提供WeekReference类来实现弱引用。
虚引用(phantom reference)——虚引用不会对对象生存周期有任何影响,也不能通过虚引实例化一个对象,虚引用存在的唯一作用是能在对象被回收时收到一条系统通知。jdk1.2之后,提供PhantomReference类来实现弱引用。

最后说一下回收方法区

方法区在HotSpot中也被称为永久代,因为java虚拟区并不要求垃圾回收器对方法区进行回收,而且方法区的垃圾回收“性价比”比较低。在堆中,尤其是新生代,垃圾回收的效率可以达到75%至90%。永久区的回收对象主要分两类:废弃的常量和无用的类。
回收废弃的常量——与回收java堆中对象比较相似,比如字符串“asd”已经进入了静态常量池,在系统中中没有被任何String类型对象叫做“asd”,则“asd”可以被清除出常量池;
无用的类——需要满足三个条件:1、该类的所有实例都被清除;2、加载该类的ClassLoad已被收回;3、该类所对应的java.lang.class对象没有在任何地方被引用,即不能通过反射访问该类的方法。
满足以上三个条件,虚拟机对无用的类也仅仅是“可以被收回”,不能像其他的对象被收回。能否被收回,虚拟机提供了-Xnoclassgc来判断,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:
+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

你可能感兴趣的:(学习笔记)