一、垃圾回收判断
引用计数法:每次引用+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的时间)的进行垃圾回收,首先也是到达一个安全点,进行初始标记(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
4、Garbage First(G1)
G1 垃圾回收阶段
同时注重吞吐量和低延迟,并发的执行
适合超大内存,划分多个相等的区域
整体上使用的标记整理算法,两个区域之间使用的复制算法
配置参数参考: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的优化:
并发标记必须在堆空间占满前完成,否则退化为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%