JVM 垃圾回收
1. 如何判断垃圾可以回收
(1)引用计数算法:引用时,计数+1,引用失效,计数-1,零时回收;缺点是遇到互相引用导致都不为零
(2)可达性分析算法(Java用):扫描堆中的对象,沿着GC Root对象为起点的引用链看能否找到该对象,如果能则其仍然存活,不能则判定为可回收的对象。
可作为GC Root的对象:栈帧中的局部变量,参数,临时变量;静态变量;常量;同步锁持有对象;Class对象
2. 五种引用(由强至弱)
- 强引用:传统的正常引用,只要引用关系还在,就不会被回收
- 软引用:在系统将要内存溢出前,会把这些对象进行二次回收,如果回收后还是没有足够内存,才会抛出内存溢出异常(java.lang.ref.SoftReference)
- 弱引用:当垃圾回收器开始工作时,就会回收弱引用关联的对象
- 虚引用:为一个对象设置虚引用,在该对象被回收是会收到一个系统通知,是否有虚引用不会对其生存时间有影响。虚引用Cleaner,在ByteBuffer对象被回收后,其占用的直接内存地址还在,此时把虚引用Cleaner放到引用队列,执行Unsafe.freeMemory来释放直接内存。
- 终结器引用(补):当对象被垃圾回收时,把对象加入引用队列(入队等待),之后通过终结器引用找到待回收的对象,调用对象的finalize()方法。注:由于处理引用队列线程优先级很低,所以不推荐用finalize()会回收对象。
3. 垃圾回收算法
(1)标记清除:先标记,然后清除,清除时就是把对象的起终地址记入空闲地址队列
优点:速度快 缺点:产生内存碎片
(2)标记整理:先标记,再把剩余的对象紧凑到一起
优点:解决了外部碎片 缺点:速度较慢
(3)标记复制:将内存划分出大小相等的两块,其中一块为原始,一块空白,把存活的对象复制到空白区(移动堆指针,按序分配无碎片)
优点:无内存碎片,高效 缺点:占用太多空间
4. 分代回收
(1)对象首先分配在Eden区
(2)新生代空间不足时,会触发minor gc,Eden和From中存活的对象被复制到To中,这些对象年龄+1,然后From和To指针交换
(3)Minor gc会阻塞其他用户进程stop the world(因为对象地址在发生改变,不停止其他进程会错误),等垃圾回收结束再恢复运行
(4)当对象寿命超过阈值时,它会晋升到老年代,最大寿命是15
(5)当老年代空间也不足时,会先尝试Minor gc,若空间仍不足,会触发Full gc(标记+整理,较慢),若还不足,会触发out of Memory
5. 几种垃圾回收器
(1)串行垃圾回收器:Serial(复制算法)+SerialOld(标记整理算法)
发生垃圾回收时,先让所有线程在一个安全点停止,等待垃圾回收结束以后其他线程继续工作
(2)吞吐量优先回收器(垃圾回收时间占总运行时间比例最小,并行):Parallel+ParallelOld
MaxGCPauseMillis:设置回收花费总时间尽量不超过该设定值
GCTimeRatio:垃圾收集时间占总时间的比率
(3)响应时间优先垃圾回收器(单次垃圾回收时间最短,并发):
ParNew+CMS(标记清除) 垃圾回收和用户进程可以同时进行
(4)G1垃圾回收器(在延迟可控的情况下获得尽可能高的吞吐量)
1)G1把堆内存分成若干大小相同的Region,每个Region都可以是Eden,Survivor或Old,通过G1HeapRegionSize可以设置每个Region大小(弱化分代概念,引入分区思想)
2)工作过程(三个流程循环进行):
a. 新生代回收:和其他垃圾收集器新生代回收一样,Eden->Survivor->Old
b. 新生代回收+并发标记(老年代占用到整堆45%时会开始)
- 初始标记:仅标记GC Root能关联到的对象,有STW但很短
- 并发标记:可以和用户进程并发,扫描整个堆的对象图,耗时长
c. 混合回收(会对E,S,O全面回收)
- 最终标记:短暂STW,标记并发阶段又产生变动的对象
- 筛选回收:暂停用户线程,多线程并行,把存活的Region复制到空的Region,然后把旧的Region清理
3)G1和CMS的新生代回收与Serial和Parallel一样,在老年代收集时,先会触发并发标记到筛选回收的过程,如果并发处理速度小于垃圾生成速度,才会Full gc(STW时间更长)
4)优化:JDK 8u20版本里,所有新分配的字符串放入一个队列,在新生代收集时判断是否有相同字符串,如果有,让他们引用同一个对象
8u40版本中,经过并发标记,如果发现一个类加载器的所有类都不再使用,则卸载所有它加载的类
8u60版本中,如果一个对象大小超过Region一半,则称为巨型对象,G1不会对巨型对象拷贝,且会优先回收巨型对象
6. GC调优
(1)由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况
(2)新生代空间Xmn不能太大或太小,建议25%-50%
如果小了,新生代会发生频繁的Minor gc,有STW;如果大了,老年代空间会小,发生Full gc概率更大,而且空间大了回收时间会较长
(3)大对象可以考虑放入老年代,否则占用新生代空间,-XX:PretenureSizeThreshold可以设置直接进入老年代的对象大小
(4)-XX:MaxTenuringThreshold 设置对象进入老年代的年龄大小,减少老年代的内存占用,降低Full gc 发生的频率
(5)设置稳定的堆大小,堆大小设置有两个参数:-Xms 初始化堆大小,-Xmx 最大堆大小。
(6)如果满足下面的指标,则一般不需要进行 GC 优化:
- Minor GC 执行时间不到50ms;
- Minor GC 执行不频繁,约10秒一次;
- Full GC 执行时间不到1s;
- Full GC 执行频率不算频繁,不低于10分钟1次。