Java虚拟机学习笔记第二章第二部分
引用
1.强引用
强引用即直接new一个对象,强引用还在,GC永远不会回收。
2.软引用(softReference)
如果有内存溢出的可能,先回收软引用。
SoftReference<SoftObject> reference = new SoftReference<>(new SoftObject());
软引用有两个构造方法与一个get方法,以下弱引用同理。
3.弱引用(weakReference)
在下一次垃圾回收的时候自动回收,无论是否有内存溢出可能。
4.虚引用(PhantomReference)
不影响对象的生存周期,无法得到对象实例,使用方法,唯一目的是对象被回收之后发送一个系统通知。
Object object = new Object();
ReferenceQueue queue = new ReferenceQueue();
// 虚引用,必须与一个引用队列关联
PhantomReference A = new PhantomReference(object, queue);
虚引用需要关联一个队列,在对象被消除的时候,将虚引用添加到队列中。
应用于对象被清楚后得到通知进行更深一层次的回收。
对象的自我拯救
对象被清理时会标记两次。首先对空对象第一次标记。
执行finalize()方法,并添加到F-queue队列,对象在被赋值为null之后,如果重写了finalize方法,与对象创建了关联,那么对象就不会被清除,即完成了自我拯救。(finalize方法并不会一直执行,也就是说会被中断,保证不会停滞)
如果没有重写finalize方法或者已经被调用过一次,那么会进行第二次标记并回收。
方法区的回收
方法区的回收分为两类,常量的回收与废弃类的回收。
对于常量: 类似对象回收,常量池中的常量未被引用,之后就会被回收。
对于类:需要满足三个条件。
1.不存在实例。
2.Classloader(类加载)被回收
3.对象没被引用,类方法不能被调用。
满足三个条件之后可以被回收。-Xnoclassgc控制类的回收。常用于经常自定义Classloader的场景。
垃圾回收算法
标记清除算法
第一轮标记,第二轮清除。
存在两个问题,效率不高,空间清理之后碎片化。
复制算法
复制算法会将内存进行分块,仅使用一块,当满了之后会将其中的仍然存活对象复制到另一块中,并将原来的那块清理,留作备用。操作上只需要移动堆顶指针,空间上并没有碎片化的问题。由于新生代的朝生夕死特性,可将内存分为8:1:1三部分,其中的8(Eden)和一块1(Survior)用来存储,另一块1(Survior)用来备用,做到了只浪费1/10的空间的复制算法,如果存活的对象大于1/10,会用老年代的空间进行担保,具体 担保之后学习。
标记整理算法
显然老年代的对象特点不允许它支持复制算法,因此诞生了标记整理算法,它将所有存活的对象向一端移动,清除边界以外的内存。
分代收集,一般在新生代采用复制算法,在老年代采用标记清除或者标记整理算法。
垃圾收集器
Serial收集器(新)
最原始的收集器,常应用于Client端,Stop the world,停止线程进行垃圾收集,在单CPU的情况下省去了切换线程带来的麻烦。
ParNew收集器(新)
Serial的多线程版本。运行在Server端,可与CMS(老)配合,CMS无法与Parallel Scavenge(新生代收集器)配合。
Parallel Scavenge收集器(新)
注重吞吐量,多线程,有利于完成任务,这个收集器有一个GC自适应调节,能够自动调节而参数。多线程,应该是Server端,不确定。
Serial Old (老)
老年代收集器,Serial的老年代收集版本,用在Client端,用在Server端时还能Parallel Scavenge搭配,和作为CMS的备用。在并发失败Concurrent’‘ Mode Future 时使用。
Parallel Old(老)
用于与Parallel scavenge 配合,实现真正的吞吐量控制。
CMS(老)
CMS分为初始标记,并发标记,重新标记,并发清理四个过程,初始标记标记了GC roots,并发标记在程序运行的时候进行Tracing,之后重新标记程序运行时新产生的垃圾,时间比初始标记长,比并发标记短。之后进行并发清除。
减少了响应时间,增大了用户体验。
缺点很明显:1.并发处理,消耗了用户CPU资源,在CPU少的情况下格为显著,采取清理与程序运行交替运行的方式能够解决,但是垃圾收集时间会变长,效果一般,已废弃。2.并发标记的垃圾能够被重新标记处理,但是并发清理的过程中产生的垃圾无法被处理,这部分垃圾被称为浮动垃圾,因此,在老年代垃圾收集的时候需要预留一部分空间,因此不能等到老年代已经被充满才进行清理,在JDK1.6中,这个阈值为92%,如果预留的空间不够的话,就会发生Concurrent Mode Future,然后启动Serial Old备案。3.因为CMS是基于标记清除的,因此会产生空间碎片的问题,这会导致提前Full GC,在这时,CMS在Full GC之前添加了合并整理过程,这并不是并发的,因此会导致停顿时间变长。另外还提供了参数,来设置几次FullGC之后进行一次带合并整理的FullGC,默认是0,每次都进行。
G1(全)
G1是一个比较前沿的技术,它的目的是替换掉CMS。
优点:1.并发,同CMS。2.分代,保留了分代的概念,但并不是物理隔离3.标记整理,除去了空间碎片化的问题。4.可预测停顿,通过一个后代维护的优先列表,在有限的时间里能够优先回收价值最大的Region。
在Region之间相互调用的时候,采用Remembered Set避免全堆扫描,每一个Region都有一个对应的Set,当存在Reference数据的写操作时,中断,并检查是否引用的对象在不同Region中,如果是的话,比如老年代对象引用了新生代对象的时候,将信息记录在被引用对象(新生代)的Set中,在GC根节点枚举的过程中加入RememberedSet防止对象遗漏。
不算RememberedSet的维护,G1大概分为四步,初始标记,并发标记,最终标记,筛选回收。
初始标记,并发标记,与CMS几乎一样,在初始标记阶段,需要更新一下TAMS,TAMS是一种指针
原理参考 [深入解析java虚拟机:Mixed GC(混合回收)G1独有的回收策略 (cdmana.com)]
(https://cdmana.com/2022/01/202201210048506792.html) 图片并不来自本人,复制上传的时候自动加的Logo,如有侵权,必删。
最终标记对于并发标记产生的对象数据添加到Remembered Set Logs中与Remembered Set进行合并。
对于CMS中的问题,1.并发占资源问题,书中并没有给出答案。2.浮动垃圾,在G1中采用了暂停线程来进行筛选回收,算是一种一般的解决方法,另外G1也可以与用户并行回收垃圾,不过个人认为会产生浮动垃圾,不确定。3.空间碎片,已解决。
对于G1,由于是未成熟产品,无太多数据检验,书中的数据我看不太懂,总结如下:
。2.浮动垃圾,在G1中采用了暂停线程来进行筛选回收,算是一种一般的解决方法,另外G1也可以与用户并行回收垃圾,不过个人认为会产生浮动垃圾,不确定。3.空间碎片,已解决。
对于G1,由于是未成熟产品,无太多数据检验,书中的数据我看不太懂,总结如下:
追求停顿,用户体验的话,可以尝试G1,追求吞吐量,G1是不太可以的,Parnew+CMS的组合依旧可以满足目前的所有要求,G1的发展前景很光明。