文章目录
- 垃圾回收
- 四种引用
- 引用计数算法
- 可达性分析算法
- 垃圾回收算法
- 标记清除
- 标记整理
- 复制
- 分代回收
- GC
- GC相关参数
- GC分析
- 大对象
- 垃圾回收器
- 串行
- 吞吐量优先
- 响应时间优先
垃圾回收
四种引用
- 强引用
new创建一个对象,通过等号运算符赋值给一个变量,那么这个变量就强引用了刚刚的对象
只有所有GC Roots对象都不通过强引用引用该对象,该对象才能被垃圾回收 - 软引用
只要没被直接强引用所引用,都有可能被垃圾回收掉
垃圾回收且内存不够时,且没有被强引用,就会将软引用的对象释放掉,认为软引用不重要 - 弱引用
只要没被直接强引用所引用,都有可能被垃圾回收掉
垃圾回收时,不管内存够不够都会回收掉弱引用对象 - 虚引用
必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存 - 终结器引用
必须配合引用队列使用,垃圾回收时,终结器入队,此时被引用对象暂时没有被回收,再由Finalizer线程通过终结器引用找到被引用对象并调用他的finalize方法,第二次GC时才能回收被引用对象
软弱引用还可以配合,引用队列使用,因为软引用,弱引用本身就是一个对象,当他们所引用对象被回收时,他们就会进入这个引用队列,因为他们自身也会占有一定内存,如果想对他们的内存进行释放,就需要引用队列
虚引用和终结器引用必须配合引用队列来使用,软弱引用可以不配合,虚终引用被创建时都会关联一个引用队列
如何判断对象可以回收
引用计数算法
在对象中添加一个引用计数器,当有地方引用时计数器就加一,引用失效时计数器减一,当计数器值为0时,进行回收
弊端:
当两对象再无引用,而这两对象循环引用时,引用计数都不为0,引用计数算法也无法回收他们
可达性分析算法
java通过该算法判断对象是否存活
思路:
通过一系列GC Root 作为根节点,再从这些根节点出发,根据引用关系向下搜索,走过的路称为引用链,如果某个对象没有任何一条引用链与之相连,那么这个对象是可以被回收的
哪些对象可以作为GC Root
- 在虚拟机栈中引用的对象
- 方法区中类静态属性引用的变量
- 方法区中常量引用的对象,比如StringTable中的引用
- 本地方法栈中JNI引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,常驻的异常对象,还有类系统加载器
- 被同步锁(synchronized)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
垃圾回收算法
JVM不会只用一种算法,会结合以下算法,协同工作,具体实现就是分代垃圾回收机制
标记清除
第一阶段对要垃圾回收的部分标记,第二阶段进行清除
- 优点:速度快
- 缺点:会产生内存碎片,造成内存空间的不连续
标记整理
- 优点:没内存碎片
- 缺点:速度慢
第一阶段对要垃圾回收的部分标记,第二阶段,在清除的过程中,会将可用的对象向前移动,让内存更紧凑,避免内存碎片问题
但是整理涉及到对象的移动,从而使效率较低
复制
- 优点:没内存碎片
- 缺点:需要占用双倍内存空间
分成两个区,FROM 和 TO,在FROM中对要回收的部分进行标记,再将可用的对象复制到TO中(TO区总是空着的)
再将from中内存清除,最后交换FROM和TO
分代回收
可以根据不同的内存区域,采用不同的垃圾回收策略,使垃圾回收更有效
-
当创建了一个新的对象,默认是放在伊甸园区
-
随着放在伊甸园的对象逐渐增多,伊甸园内存不足时,会触发Minor GC(会引发stop the world,暂停其他线程,垃圾回收结束后,其他线程恢复)
-
采用可达性分析算法,将还可用的对象复制到幸存区TO中,并将幸存的对象的寿命+1,当幸存对象的寿命超过了15(不同的垃圾回收器阈值不一样),就会晋升到老年代中,释放伊甸园区剩余对象
-
其中复制算法会将FROM和TO交换一次位置
-
再次创建对象,如果伊甸园区又满了,再次触发Minor GC,这时不仅会回收伊甸园垃圾,也会回收幸存区垃圾,再将可用对象(伊甸园区和幸存区from)复制到TO中,之后交换两个幸存区,并让幸存区中对象寿命+1
-
当新生代和老年代内存都不足时,先尝试Minor GC,如果还不足,会触发Full GC(也会引发stop the world,时间更长)
GC
GC相关参数
含义 参数
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size )
幸存区比例(动态) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy
幸存区比例 -XX:SurvivorRatio=ratio
晋升阈值 -XX:MaxTenuringThreshold=threshold
晋升详情 -XX:+PrintTenuringDistribution
GC详情 -XX:+PrintGCDetails -verbose:gc
FullGC前先做一次MinorGC XX:+ScavengeBeforeFullGC
GC分析
对JVM进行这样的设置
-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
当执行内容为空时
这里伊甸园区初始就有东西是因为Java程序启动时,需要加载一些类,这些类使用的就是伊甸园的区域
Metaspace是元空间
大对象
如果一个对象已经能够确定在新生代放不下,如果发现老年代能够容纳,就会直接晋升老年代
当老年代和新生代都不能容纳时,会抛出OOM(抛出前还会进行Full GC进行自救)
一个线程内的OOM不会导致其他线程也结束
垃圾回收器
- 串行
- 单线程
- 适合堆内存较小,个人电脑
- 吞吐量优先
- 多线程
- 适合堆内存较大,多核cpu(如果是单核的,那么多个线程就要去争抢cpu的时间片,效率还不如单线程)
- 让单位时间内,STW时间最短
- 响应时间优先
- 多线程
- 堆内存较大,多核cpu
- 垃圾回收时,单次STW的时间尽可能短
串行
# 开启串行回收器
XX:+UseSerialGC = Serial + SerialOld
Serial收集器:
最基本的,历史最悠久的
单线程收集器,采用复制算法,工作在新生代
Serial Old收集器:
是Serial收集器的老年代版本
单线程,采用标记整理算法,工作在老年代
吞吐量优先
是JDK8默认使用的垃圾回收器
每次垃圾回收最大暂停毫秒数默认为200ms
要让垃圾回收最大暂停毫秒数变短,堆就得变小,而让吞吐量变大,即垃圾回收在总时间的占比减小,堆就得变大,所以这两个指标是相对的,需要折中选取
响应时间优先
ConcMarkSweepGC(CMS收集器):Concurrent Mark Sweep(并发,标记,清除)
- 工作在老年代
- 并发的:垃圾回收线程和用户线程同时执行(并行:垃圾回收线程和用户线程并行运行,但是在垃圾回收期间,用户线程不允许被执行)
- 基于标记清除算法
ParNewGC:
- 工作在新生代
- 基于复制算法
- 初始标记:标记GC Roots能直接到的对象,速度很快,存在STW
- 并发标记:进行GC Roots Tracing过程,找出存活对象且用户线程可并发执行
- 重新标记:修正并发标记期间因用户程序继续运行而导致标记产生变动的对象的标记记录,存在STW
- 并发清理:对标记对象进行清理回收
因为是并发清理,所以在清理的过程中可能会产生其他垃圾(浮动垃圾),所以不能在堆内存不足时再做垃圾回收,需要预留些新空间,来保存浮动垃圾
所以-XX:CMSInitiatingOccupancyFraction=percent
就是用来控制占用内存到总内存的百分之多少进行垃圾清理