【JVM|垃圾回收】第二天
摘要:
本文系统梳理了Java垃圾回收机制。首先介绍了两种回收判断方法:引用计数法(易产生循环引用)和可达分析法(通过根对象识别存活对象)。其次详细解析了五种引用类型:强引用、软引用、弱引用、虚引用和终结器引用。然后对比了三种回收算法:标记清除(速度快但碎片化)、标记整理(无碎片但慢)、复制法(快无碎片但耗内存)。最后概述了三种垃圾回收器:串行(适合小内存)、吞吐量优先(多核大内存)和响应时间优先(最小化单次停顿)。
一,回收判断
1,引用计数法
当引用这个对象时,计数加一,无引用时计数为0。但是引出循环引用问题-->两个对象互相引用,这两个对象又没有被其他对象引用,因为技术不为0无法被回收。
2,可达分析法
通过指定不可回收的对象,包括-->roots根对象,直接或间接引用根对象的其他对象
根对象:1,system class对象(程序运行的基础)2,native class对象(访问操作系统的类)3,Thread对象(包括局部变量即栈帧)4,Busy Monitor对象
2.1,强引用
root对象直接关联的对象,常见都为强引用,无强引用时会被回收。
2.2,软引用(new SoftReference(对象)数据不敏感且内存空间紧张时可使用)
当没有强引用引用软引用对象时,垃圾回收后空间不足,会将软引用对象回收。
2.3,弱引用(new WeakReference(对象))
当没有强引用引用弱引用对象时,无论内存充足与否都会回收掉弱引用对象,Full GC会清除所有弱引用。
补充:软弱引用也是一个对象,会占用一定内存,如果想要释放其内存,需要在引用队列找到这些对象然后回收。
2.4,虚引用
例如Cleaner对象就是虚引用对象,引用byteBuffer,当byteBuffer被回收时,CLeaner对象会被放入引用队列,后台的一个守护线程就从队列中拿到虚引用对象然后调用clear等方法清除额外的内存:直接内存。
2.5,终结器引用
对象重写finallize()方法,jvm会为该对象创建一个终结器引用,当发生垃圾回收且没有强引用引用该对象时,该对象先不会被回收,而是终结器对象会被放入引用队列,该队列由一个优先级较低的线程管理,当线程执行接收到终结器引用就会根据该引用找到对应的对象,并执行finallize()方法然后下一次执行垃圾回收时进行回收。
二,垃圾回收算法
1,标记清除
(1)标记
根据可达分析法,找到没有被根对象引用的垃圾对象,对这些对象进行标记
(2)清除
针对被标记的对象,通过将对象内存空间的启始位置记录的方式将该垃圾对象清除,后续其他对象创建时会直接从记录中寻找合适的空间放置。
优缺点:这种方法速度快,但是会造成内存碎片化
2,标记整理
相较于标记清除,该方法在清除后多了一步整理的工作,将非垃圾对象规整使之变得紧凑,避免内存碎片化
缺点:因为整理内存,会额外消耗许多资源,因此会导致速度较慢
3,复制法
该算法需要额外一块空间(from/to),当标记完成后就将from中存活的对象复制到To空间中,这个过程中自然就能够做到整理,最后交换from和To的引用(from<-->To)
优缺点:速度较快且不会由内存碎片化问题,但是要消耗额外内存空间
4,分代回收(堆空间划分为新生代和老年代)
补充:1,寿命阈值并非必定要求,如果新生代幸存区空间非常紧张,未达到寿命阈值的对象也会被放入老年代。 2,如果存入的是一个大对象(新生代清理后也一定放不下),不会触发垃圾回收而是直接被放入老年代中。
Tips:相关VM参数
三,垃圾回收器
1,串行(回收期间阻塞用户线程)
(1)单线程
(2)堆内存小,适合个人电脑
2,吞吐量优先
(1)多线程
(2)堆内存大,多核CPU
(3)单位时间内,STW 的时间最短
Tips:执行效率和CPU核数密切相关,“所有线程”并行地进行垃圾回收,吞吐量大
3,响应时间优先(牺牲部分吞吐量并行执行换取响应时间)
(1)多线程
(2)堆内存大,多核CPU
(3)尽可能让单次 STW 的时间短
tips:CMS老年代的垃圾回收器如果失败-->退化到串行的垃圾回收,拖累响应时间
4,G1垃圾回收器(jdk9默认)
三个垃圾回收阶段
(1)Yong Collection新生代回收
(2)Yong Collection+CM
Tips:通过跨代引用,老年代会被分为多个大小相等的区域放置对象,如果老年代中有root对象引用了新生代中的对象,这个区域会被标记为“脏卡”,该对象会记录root对象在老年代的引用地址,这样初始标记时就无需完全遍历老年代,而是直接扫描脏卡即可
(3)Mixed Collection混合收集
Tips:根据最大暂停时间有选择的进行老年代的垃圾回收,挑选那些回收价值较高的几个old区域----回收后能够释放的空间较多