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  被加锁的对象

二、四种引用

JVM垃圾回收

 

 

  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

JVM垃圾回收

 

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数

JVM垃圾回收

 

3、响应时间优先(CMS主要作用于老年代):基于标记-清除的算法并发(减少STW的时间)的进行垃圾回收,首先也是到达一个安全点,进行初始标记(STW,标记根对象),然后是一个并发标记(不会STW),

重新标记(STW,因为在并发标记的时候,用户线程也在执行),最后并发的清理。对cpu的占有率相对较低,响应时间降低了,吞吐量上升了一点。当内存碎片化较多的时候,不能够存储新的对象的时候,

会退化成单线程垃圾回收器,进行一次标记整理。

  多线程

  堆内存较大,多核cpu

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

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

  -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

JVM垃圾回收

 

4、Garbage First(G1)JVM垃圾回收

                     G1 垃圾回收阶段

 

JVM垃圾回收

 

 

 

 

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

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

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

配置参数参考: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

JVM垃圾回收

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

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

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

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

jdk9对G1的优化:

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

  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:新生代越大越好吗?

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

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

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

  2、老年代的内存调优

    a、越大越好

    b、先尝试不做调优,如果没有full GC,可以先尝试调优新生代

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

    d、调整老年代占用达到的阈值为75%-80%

  

  

 

JVM垃圾回收

上一篇:【Python】Object Oriented Programming


下一篇:字符串反码 A