JVM垃圾回收
1.如何判断对象可以回收
1.1 引用计数法
该对象被其他对象引用,计数+1,但存在循环引用问题
1.2 可达性分析
确定根对象,肯定不能被垃圾回收的对象,堆内存扫描,查看对象是否被根对象直接或间接引用,有则不能被回收,反之可以回收
- Java虚拟机中的垃圾回收期采用可达性分析来探索所有存活的对象
- 扫描堆中的对象,看是否能够沿着gc root对象为起点的引用链找到该对象,找不到,表示可以回收
- 哪些对象可以作为GC Root
1.System class
2.Native stack
3.thread
4.busy monitor
2.四种引用
前软弱虚 终结器引用
- 强引用:只有GC Roots对象都不通过强引用引用该对象,该对象才能被垃圾回收
- 软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放自身
- 弱引用 :仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都可能会回收弱引用对象,可以配合引用队列来释放自身
- 虚引用:必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存
- 终结器引用:无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象在世没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用器fialize方法,第二次gc时才能回收被引用对象
软引用配合引用队列应用
while中内容查看引用队列内容,将list中为null的软引用删除,for循环中将不在打印出为null被释放的内容
3.垃圾回收算法
3.1 标记清除算法
Mark Sweep
垃圾回收时对堆中对象扫描,没有被gc root引用则标记,然后第二阶段对其释放,只需要将该对象的起始结束地址记录下来,放入空闲列表中即可
- 速度较快
- 会造成内存碎片
3.2 标记整理算法
Mark Compact
- 速度慢
- 没有内存碎片
3.3 复制算法
copy
- 不会有内存碎片
- 需要占用双倍内存空间
-
4 分代回收

- 对象首先分配在伊甸园区
- 新生代空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to区域
- minor gc会引发stop the world,暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命时15(4bit),不同的垃圾回收该值不同
- 当老年代空间不足,会先尝试触发minor gc,如果空间仍不足,会触发full gc,STW的时间更长
- 新生代复制算法,老年代标记清除或整理
相关VM参数
大对象新生代放不下直接晋升老年代
一个线程的oom不会导致java进程的结束
5.垃圾回收
5.1串行
- 单线程
- 堆内存较小,适合个人电脑
-XX:+UseSerialGC=Serial+SerialOld 新生代 复制算法,老年代标记整理算法
5.2吞吐量优先
- 多线程
- 堆内存较大,多核cpu
- 单位时间内STW的时间最短 0.2 0.2 =0.4
-XX:+UseParallelGC ~UseParllelOldGc 新生代 复制算法,老年代标记整理算法,算法和串行的相同 Parallel并行
-XX:+UseAdaptiveSizePolicy 开启自适应新生代中伊甸园和survive区域的比例,动态调整
-XX:+GCTimeRatio=ratio 设定目标gc时间目标 1/(1+ratio),如99,则结果0.01垃圾回收时间不能占用总时间的1%
-XX:+MaxGCPauseMillis=ms 最大200ms,和上面这个冲突,最大暂停时间

5.3 响应时间优先
- 多线程
- 堆内存较大,多核cpu
- 尽可能让STW的单次时间最短 如每次0.1 0.1 0.1 0.1 0.1 一小时内五次0.5s
并发标记阶段允许用户线程并发执行
-XX:UseConcMarkSweepGC ~-XX:+UseParNewGc ~SerialOld 标记清除算法 ,产生碎片过多没法继续优化时会退化为串行的进行标记整理,此时该次垃圾回收时间飙升
-XX:ParallelGcThreads=n ~-XX:ConcGCThreads=threads
-XX:CMSInitialtingOccupancyFraction=percent 执行垃圾回收时候的占比,因为并行清理阶段又生成了浮动垃圾,为此预留空间
-XX:+CMSScavengeBeforeRemark 重新标记之前对新生代先回收一下,避免从新生代查询老年代对整个堆进行扫描
6 G1垃圾回收器
定义:Garbage First
JDK9默认,取代之前的CMS垃圾回收器
适用场景:
- 同时注意吞吐量和低延迟,默认暂停目标200ms
- 超大堆内存,会将堆划分为多个大小相等的Regio
- 整体上时标记+整理算法,两个区域之间时复制算法
相关JVM参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
6.1 G1垃圾回收阶段

1> Young Collection
会STW
把堆划分成了一个个大小相等的region,每个区域都可以独立作为伊甸园幸存区,老年代。

2>Young Collection+CM(concurrent mark)
- 在Young GC时会进行GC Root的初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由下面的JVM参数决定
-XX:InitialtingHeapOccupancyPrecent=percent(默认45%)
3>Mixed Collection
会对ESO进行全面垃圾回收
- 最终标记(Remark)会STW
- 拷贝存活(Evacuation)会STW
-XX:MaxGCPauseMillis=ms
G1和CMS并发失败后才会full GC,回收速度低于垃圾产生速度时才会
4>Young Collection跨代引用
新生代回收的跨代引用(老年代引用新生代)问题
加速新生代垃圾回收

- 卡表与Remembered Set(新生代记录卡表中有哪些引用了新生代中的对象)(新生代根据这些脏卡快速找到gc root)
- 在引用变更时通过post-write barrier+ dirty card queue
- concurrent refinement threads更新Remembered Set
7> Remark
pre-write barrier+ stab_mark_queue
8>JDK 8u20字符串去重
- 优点:节省大量内存
- 缺点:略微多占用了cpu时间,新生代回收时间略微增加
-XX:+UseStringDepuliaction
- 将所有新分配的字符串放入一个队列
- 当新生代回收时,G1并发检查是否有字符串重复
- 如果值相同,则让他们引用同一个char[]
- 注意:和String.inter()不同
1.String.intern()关注的时字符串对象
2.而字符串去重关注的时堆中的char[]
3.在JVM内部,使用了不同的字符串表
9>JDK8u40并发标记卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
-XX:+ClassUnloadingWithConcurrentMark 默认启用
10> JDK 8u60回收巨型对象
- 一个对象大于regin的一半时,称之为巨型对象
- G1不会堆居型对象进行拷贝
- 回收时被优先考虑
- G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉
-
11> JDK9并发标记起始时间的调整
- 并发标记必须在堆空间沾满前完成,否则退化为FullGC
- JDK9之前需要使用-XX:InitialtingHeapOccupancyPercent
- JDK9可以动态调整
7 垃圾回收调优
7.1 调优领域
- 内存
- 锁竞争
- cpu占用
- io
确定目标
- 低延迟还是高吞吐量,选择合适的回收器、CMS、G1、ZGC、ParallelGC
最快的GC时不发生GC
查看FullGC前后的内存占比,考虑下面几个问题
- 数据是否太多
- 数据是否太臃肿
- 是否存在内存泄漏
7.2 新生代调优
- 新生代特点:所有的new操作的内存分配非常廉价
- TLAB thread-local allocation buffer 避免线程内存分配时线程之间并发冲突问题,线程局部分配缓冲区,有自己私有的伊甸园区域
- 死亡对象的回收带价是0
- 大部分对象用过即死
- MinorGC的时间远远低于Full GC
-Xmn young generation greater than 25% and less 50% of the overall heap size 一般建议新生代内存分配25%-50%之间均衡
主要耗费时间在复制上而不是在标记上
幸存区要大到能保留 当前活跃对象+需要晋升对象
过小的话,导致幸存区内存不足,有些对象未满足晋升条件提及前放入老年代,最终导致老年代内存不足提前full gc才能清楚该类对象
晋升阈值配置得当,让长时间存活对象尽快晋升
-XX:MaxTenuringThreshold=threshold 调整最大晋升阈值
-XX:+PrintTenuringDistribution 打印gc信息如下

7.3 案例
1.Full GC和Minor GC频繁
适当增加新生代大小
2.请求高峰期发生Full GC,单词暂停时间特别长(CMS)
配置重新标记前先清理新生代
3.老年代充裕情况下,发生Full GC(CMS jdk1.7)
永久代空间不足也会导致Full gc