JVM垃圾回收

一、垃圾回收判断

  引用计数法:每次引用+1,解引用-1,引用为0可被回收,解决不了循环引用问题,会造成内存泄漏

  可达性分析算法:被根对象直接或间接引用的对象都不能回收

Q:那些对象可以作为GC Root?

  Memory Analyzer工具可以作为堆内存分析工具,可以找到作为GC Root的对象

  抓取内存快照jmap -dump:format-b,live,file-1.bin pid

  a、System class 系统核心类,Object类,String类,HashMap等等

  b、Native Stack 操作引用的

  c、Thread 活动线程使用的对象,例如被局部变量引用的对象

  d、Business Monitor  被加锁的对象

二、四种引用

1、强引用 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收

2、软引用(SoftReference) 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用 对象 可以配合引用队列来释放软引用自身

3、弱引用(WeakReference) 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象 可以配合引用队列来释放弱引用自身

4、虚引用(PhantomReference) 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存

5、终结器引用(FinalReference) 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

  例如:创建ByteBuffer的时候会创建一个名为Cleaner的虚引用对象,当ByteBuffer没有被强引用所引用就会被jvm垃圾回收,虚引用Cleaner就会进入引用队列,会有专门的线程扫描引用队列,被发现后会调用直接内存地址的方法将直接内存释放掉,保证直接内存不会导致内存泄漏

是所有引用类型中最弱的,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例。

  终结器引用:创建的时候会关联一个引用队列,当A4对象没有被强引用所引用时,A4被垃圾回收的时候,会将终结器引用放入到一个引用队列(被引用对象暂时还没有被垃圾回收),有专门的线程(优先级较低,可能会造成对象迟迟不被回收)扫描引用队列并调用finallize()方法,第二次GC的时候才能回收掉被引用对象

三、垃圾回收算法

  标记-清除:速度快,但会产生内存碎片

  标记-整理:解决内存碎片问题,但效率低,牵扯到内存地址的变动

  标记-复制:两块区域FROM和TO,首先标记,再进行复制从FROM到TO,最后进行交换,不会产生碎片问题,但会产生两块区域浪费内存空间

  分代垃圾回收机制:新生代(eden,s1,s2),老年代

新的对象先被放入eden区,如果eden内存空间不够,会进行一次Minor GC,先标记出不能回收的对像,复制到TO区,对象年龄+1,再与FROM交换位置,

当年龄大于15的时候就会被移到老年代,当老年代内存空间不足的时候,会先进行至少一次Minor GC,如果内存还是不足则会进行一次Full GC,此时如果内存还是不足则会产生内存溢出。

GC会产生Stop The World,根据老年代的特点采用标记-整理-清除算法。

 四、垃圾回收器

1、串行:单线程回收 

  单线程

  堆内存较小,适合个人使用

  -XX:+UseSerialGC

2、吞吐量优先:基于标记-整理的算法来进行垃圾回收,运行到一个节点(安全点),STW所有线程(一般就是核心数)一起进行垃圾回收,cpu在这时会突然飙高到100%

  多线程

  堆内存较大,多核cpu

  让单位时间内,STW的时间最短

  并行的执行,会STW

  -XX:+UseParallelGC 作用在新生代 

  -XX:+UseParallelOldGC 作用在老年代

  -XX:UseAdaptiveSizePolicy 设置此项以后,并行收集器会自动选择年轻代大小和相应的Surivior区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时一直打开。

  -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) n一般设置19  一百分钟内允许五分钟的暂停

  -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值

  -XX:ParallelGCThreads=n:设置并行收集器并行收集时,使用的线程数,一般等于CPU数

3、响应时间优先(CMS主要作用于老年代):基于标记-清除的算法并发(减少STW的时间)的进行垃圾回收,分为4个阶段:

(1)初始标记:只标记出根对象直接引用的对象,STW

(2)并发标记:继续标记其他对象,与应用程序是并发执行

(3)重新标记:对并发执行阶段的对象进行重新标记,STW,因为在并发标记的时候,用户线程也在执行

(4)并发清除:将产生的垃圾清除。清除过程中,应用程序又会不断的产生新的垃圾,叫做浮动垃圾,这些垃圾就要留到下一次GC过程中清除。

当内存碎片化较多的时候,不能够存储新的对象的时候,会退化成单线程垃圾回收器,进行一次标记整理。

特点:

(1)多线程

(2)堆内存较大,多核cpu

(3)尽可能让单次STW的时间最短

(4)并发的执行,不会停掉用户线程(只是其中一些阶段)

  -XX:+UseParNewGC 作用在新生代 (基于标记-复制算法)

  -XX:+UseConcMarkSweepGC 作用在老年代 ,有时候并发失败会退化到-SerialOld(串行化)

  -XX:ParallelGCThreads=n:并行收集线程数

  -XX:ConcGCThreads=n:设置并发收集器收集时使用的线程数,建议并行收集线程数的1/4

  -XX:CMSFULLGCsBeforCompaction=n:由于并发收集器不对内粗空间进行压缩、整理,所以运行一段时间会产生“碎片”,使得运行效率低。此值设置运行n次GC以后对内训空间进行压缩、整理。

  -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。

  -XX:CMSInitiatingOccupancyFraction=70:当老年代内存使用达到n%,开始回收。CMSInitiatingOccupancyFraction = (100 - MinHeapFreeRatio) + (CMSTriggerRatio * MinHeapFreeRatio / 100)`

  -XX:+CMSScavengeBeforeRemark:重新标记之前先进行一次ygc

4、Garbage First(G1)

  整体上不再分成新生代和老年代,而是将内存分成一个个小的Region,但是每个Regin在逻辑上是属于老年代或者新生代的其中一种。分为4个阶段:

(1)初始标记:标记出GC Root直接应用的对象,STW

(2)标记Region:通过RSet标记出上一个阶段标记出的Region引用到的old区Region

(3)并发标记:标记范围是上一步标记出来的old区Region

(4)重新标记:跟CMS差不多

(5)垃圾清除:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region。而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理。

(1)同时注重吞吐量和低延迟,并发的执行

(2)适合超大内存,划分多个相等的区域

(3)整体上使用的标记整理算法,两个区域之间使用的复制算法

配置参数参考:https://blog.csdn.net/megustas_jjc/article/details/105470675

java9之前,-XX:+UseG1GC,java9之后默认是G1

 -XX:MaxGCPauseMillis=n:设置最大GC 暂停时间。这是一个大概值,JVM 会尽可能的满足此值

-XX:G1HeapRegionSize=n:使用G1,Java堆被划分为大小均匀的区域。这个参数配置各个子区域的大小。此参数的默认值根据堆大小的人工进行确定。最小值为 1Mb 且最大值为 32Mb。

回收阶段:https://www.jianshu.com/p/989429f646af

Q:新生代垃圾回收,跨代引用问题?

  采用以卡表与Remembered Set的方法解决,https://blog.csdn.net/weixin_49193222/article/details/111313505

Q:Remark

  • 黑色:表示根对象,或者该对象与它引用的对象都已经被扫描过了。
  • 灰色:该对象本身已经被标记,但是它引用的对象还没有扫描完。
  • 白色:未被扫描的对象,如果扫描完所有对象之后,最终为白色的为不可达对象,也就是垃圾对象。

jdk8u20-XX:+UseStringDeduplication字符串去重功能(会占用cpu时间)

jdk8u40-XX:+ClassUnloadingWithConcurrentMark 在并发标记阶段结束后,JVM就进行类卸载

jdk8u60 回收巨型对象:不会进行copy,回收时优先考虑,会跟踪老年代所有的incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时被回收

jdk9对G1的优化:

(1)并发标记必须在堆空间占满前完成,否则退化为fullGC。

(2)jdk9之前需要使用XX:InitiatingHeapOccupancyPercent=n(启动并发GC周期时的堆内存占用百分比,G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比值为 0 则表示"一直执行GC循环",默认值为 45)设置,jdk9之后,可以动态调整这个值,会添加一个安全的空挡空间。

五、JVM调优

  1、官网查看JVM参数

  2、java –XX:+PrintFlagsFinal -versio | findStr "GC" 查看GC配置情况

  3、内存

  4、锁竞争

  5、cpu

  6、吞吐量和响应时间的取舍

  7、ZGC是从JDK11中引入的一种新的支持弹性伸缩和低延迟垃圾收集器

  8、另一种虚拟机Zing

六、GC调优

1、新生代内存调优

Q:新生代越大越好吗?

A:否,新生代太小,会频繁的发生minor gc,太大发生GC的时间会延后,并且会占用老年代的空间,使得发生full GC的几率变高,官网建议25%网上,50%往下,实际配置时要进行权衡

Eden区:能容纳并发量 *(请求 - 响应)的数据

幸存区:大到能保存【当前活跃对象 + 需要晋升对象】,晋升阈值调配要得当

2、老年代的内存调优

(1)越大越好

(2)先尝试不做调优,如果没有full GC,可以先尝试调优新生代

(3)观察发生full GC时 老年代内存占用,响应调大1/4-1/3

(4)调整老年代占用达到的阈值为75%-80%

  

  

 

posted @ 2021-09-10 14:53  上官兰夏  阅读(62)  评论(0编辑  收藏  举报