JVM垃圾回收

JVM垃圾回收

1.如何判断对象可以回收

1.1 引用计数法

该对象被其他对象引用,计数+1,但存在循环引用问题

1.2 可达性分析

确定根对象,肯定不能被垃圾回收的对象,堆内存扫描,查看对象是否被根对象直接或间接引用,有则不能被回收,反之可以回收

  • Java虚拟机中的垃圾回收期采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着gc root对象为起点的引用链找到该对象,找不到,表示可以回收
  • 哪些对象可以作为GC Root
    1.System class
    2.Native stack
    3.thread
    4.busy monitor

2.四种引用

前软弱虚 终结器引用

  1. 强引用:只有GC Roots对象都不通过强引用引用该对象,该对象才能被垃圾回收
  2. 软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放自身
  3. 弱引用 :仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都可能会回收弱引用对象,可以配合引用队列来释放自身
  4. 虚引用:必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存
  5. 终结器引用:无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象在世没有被回收),再由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

相关推荐
bing_1583 小时前
JVM 类加载器在什么情况下会加载一个类?
java·jvm
明天不下雨(牛客同名)9 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
种豆走天下16 小时前
JVM基础原理
jvm
鸭梨大大大1 天前
JVM的学习
jvm·学习
geek_super1 天前
JVM学习--JVM运行时参数
jvm·学习
JIU_WW1 天前
JVM面试专题
java·jvm·面试·java虚拟机·垃圾回收
纸醉金迷金手指2 天前
GHCTF-web-wp
前端·jvm
少JSQ2 天前
深入浅出Java虚拟机(JVM)-认识JVM
jvm
兮动人2 天前
arthas之jvm相关命令
jvm·arthas·arthas之jvm相关命令·arthas基础命令