《深入java虚拟机--JVM高级特性与最佳实践》学习笔记(三) GC原理与垃圾收集器

时间:2022-12-29 09:42:40

JAVA对堆进行垃圾回收,其回收算法在很多教科书中都误写为引用计数。在大多数情况下,这是一个好算法,也有一些比较著名的应用案例,比如COM,AS3,Python语言。引用计数的一个弊端是,无法解决对象相互循环引用的问题。比如两对象A、B,A中持有一个指向B的引用,B中持有一个指向A的引用。除此之外,没有任何指向这两个对象的其他引用,实际上这两个对象已经不可能再被访问,但是他们因为相互引用着对方,导致他们的引用计数都不为0,于是引用计数算法无法通知GC回收器回收它们。


JAVA真正的GC算法是可以解决上述问题的。简述如下

根搜索算法(GC Roots):在内存中维护一个对象相互引用的图,对象作为结点,对象之间的引用作为结点之间的边。一次一次的扫描整个图,回收其中非联通的部分。

      可作为Roots( 扫描的起点)的对象包括:

            1. 虚拟机栈(栈帧中的本地变量表)中的引用对象

            2. 方法去中的类静态属性引用的对象。

            3. 方法去中的常量引用的对象。

            4.本地方法栈中JNI(即一般说的Native方法)的引用的对象。

 

再谈引用:

    无论是引用计数算法还是跟搜索算法,判断对象是否存活都与“引用相关”。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong),软引用(Sodt Referrence)、弱引用(Weak Reference)、虚引用(Phantom)。四种引用强度依次逐渐减弱。

          《深入java虚拟机--JVM高级特性与最佳实践》学习笔记(三) GC原理与垃圾收集器

         强引用:代码之中普遍存在的,类似 Object 哦= newObject();只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。

         软引用:在内存不足时,首先回收被软引用指向的对象。常用于实现内存敏感的高速缓存。

                        引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

         弱引用:GC就一定会回收它。

         虚引用:"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

         虚引用主要用来跟踪垃圾回收活动。在对象被垃圾回收时发出一个系统通知。

 

生存还是死亡:

         宣告一个对象死亡,至少要经历两次标记过程:1. 在发现对象非联通时,被第一次标记。2. 将其中有重写finalize()方法且之前没有调用过的对象放入F-Queue队列中,并稍候由一条有虚拟机自动创立的、优先级低的Finalizer线程去执行。这里的“执行”是指会触发方法,但并不承诺会等待它运行结束。(这便是finalize方法不一定会执行完的原因)。

        finalize方法中,对象可以有一次拯救自己的机会:只要重新与引用链上的任何一个对象建立关联即可。如果对象这时候还没有逃脱,那它就真的离死不远了。这种拯救只可能有一次,因为下一次再被GC时,finalize方法便不会执行。finalize不是一个可靠的方法,最好不要用。

 

方法区的回收:

        分两部分:废弃常量的回收(无引用指向的常量)和无用的类的卸载。判断一个类是无用的类的标准:1.该类所有实例已被回收。2. 加载该类的ClassLoader已被回收。3. 该类对应的Class对象没有在任何地方被引用。符合上述标准就“可以”被回收,但不一定被回收。

 

垃圾收集算法:

       1. 标记-清除(Mark-Sweep):标记所有要回收的对象,然后清除他们。缺点:效率低,会产生内存碎片。

       2. 复制算法(Copying):将内存按容量划分为大小相同的两块。每次只使用其中一块,这块内存完了,就将判断为存活的对象复制到另一块,清空当前块。实现简单,运行高效,代价为可用内存缩小为原来的一半。目前的商业虚拟机都采用这种算法来回收新生代。

       3. 标记整理算法(Mark-Compact):与“标记-清除”一样,在其后加一步让所有存货的对象都想一段移动,然后直接清除掉端边界以外的内存。存活率低时效果较好,可用于老年代。

       4. 分代收集算法(Generational Collection):将对象存活周期的不同将内存分为几块,根据各个年代的特点采用最适当的收集算法。当前商业虚拟机都采用此算法。

 

垃圾收集器:

       年轻代收集器:

             1. Serial :单线程,复制算法。进行垃圾收集时,暂停其他所有的工作线程,直到收集结束。优点,简单高效;缺点,Stop the world。(搭配老年代:CMS,Serial Old)

             2. ParNew:实际上是Serial收集器的多线程版本。多CPU环境有优势。(搭配老年代:CMS,Serial Old)

             3. Parallel Scavenge:用复制算法,多线程,可精确控制吞吐量

                       吞吐量 = 运行用户代码时间/(运行用户代码时间+ GC时间)

                       - XX:MaxGCPauseMillis 最大停顿时间  (降低停顿时间以牺牲吞吐量和新生代空间来换取)

                       -XX:GCTimeRatio 吞吐量

                       -XX:+UseAdaptiveSizePolicy 自动根据系统运行情况,动态调整各参数以提供最合适的停顿时间和最大的吞吐量。自适应策略也是Parrallel Scavenge收集器与ParNew收集器的一个重要区别。

          

       老年代收集器:

            1. Serial Old: Serial的老年代版本。标记-整理

            2. Parallel Old:Parrallel Scavenge的老年代版本。 多线程,标记- 整理

            3. CMS: 一种以获取最短回收停顿时间为目标的收集器。常用于网站或B/S系统。标记 - 清除(会产生碎片)

                     初始标记(stop others 单线程)- - 并发标记 - - 重新标记(stop others 多线程) -- 并发清除

           

       横跨两代的收集器:

           G1收集器:当前最新成果。1). 标记 - 整理,不会产生碎片;2)精确控制停顿。(几乎达到RTSJ的特征)

                      将堆分成多个大小固定的独立区(Ragion),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,悠闲回收垃圾最多的区域。