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

相关推荐
小羊学伽瓦5 小时前
【Java基础】——JVM
java·jvm
268572598 小时前
JVM 监控
java·开发语言·jvm
promise5248 小时前
JVM之jcmd命令详解
java·linux·运维·服务器·jvm·bash·jcmd
LUCIAZZZ15 小时前
JVM之内存管理(二)
java·jvm·后端·spring·操作系统·springboot
蒋大钊!17 小时前
JVM调优-重启CPU飙高优化
jvm
黄雪超20 小时前
JVM——Java语法糖与Java编译器
java·开发语言·jvm
大G哥20 小时前
Rust 之 trait 与泛型的奥秘
java·开发语言·jvm·数据结构·rust
isyangli_blog21 小时前
(1-1)Java的JDK、JRE、JVM三者间的关系
java·开发语言·jvm
钰爱&1 天前
【Linux】POSIX 线程信号量与互斥锁▲
java·开发语言·jvm
黄雪超1 天前
JVM——即时编译器的中间表达形式
java·开发语言·jvm