垃圾收集器与内存分配策略
一、判断对象是否存活
-
判断对象可以回收
- 引用计数是否为0
- 可达性分析:GC root 到对象不可达
GC root : 虚拟机栈中引用的对象,方法区静态变量引用的对象,方法区中常量。
-
方法区,一般也叫永久代,存储一般不做回收,主要存储类文件信息,常量,静态变量等
-
一般回收新生代
三、GC逻辑与过程
- 内存分配与回收策略
- 堆分配的内存在物理上可以是不连续的,但是在逻辑上必须连续
- 对象优先在新生代eden区分配
- 打对象直接进入老年代
- 长期存货的对象将进入老年代
- 动态对象年龄判定
- -XX:MaxtenuringThreshold=1,-XX:MaxtenuringThreshold=15
- 下线保有权为1的话,每熬过一次GC加一
- 空间分配担保
- GC之前会检查一边老年带最大可用的连续空间是否大于新生代所有对象的和
堆模型
- 堆 = 新生代 (eden + surviver + surviver)+ 老年代 tenured
gc方式分为三种:
1、minor gc 新生代gc
-
minor gc时机:
当eden区满时触发minor gc -
一般采用复制算法
-
新生代模型 eden + surviver + surviver 8 : 1 : 1
-
每次使用eden和其中一个surviver区,将存活的对象复制到另一个surviver区
新生代相关参数: 1)-XX:NewSize和-XX:MaxNewSize 用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。 2)-XX:SurvivorRatio 用于设置Eden和其中一个Survivor的比值,这个值也比较重要。 3)-XX:+PrintTenuringDistribution 这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。 4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold 用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。
-
minor gc运行的很慢有可能是什么原因引起的?
- 新生代空间设置过大。
- 对象引用链较长,进行可达性分析时间较长。
- 新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。
- 内存分配担保失败,由minor gc转化为full gc
- 采用的垃圾收集器效率较低,比如新生代使用serial收集器
2、major gc 老年代gc
3、full gc 新生代、老年代、永久代同时GC
-
触发时机:
- 当调用system.gc(),系统建议执行full gc, 但不一定执行
- 老年代空间不足
- 方法区空间不足
- 通过历史minor gc中进入老年代的平均大小判断,如果大于老年代所剩空间,则触发
- 当次minor gc 要进入老年代的对象大小 大于老年代所剩空间,触发full gc
ps、1.8之后已经没有永久代,类的元信息存在元空间中,该空间与堆不相连
-
垃圾收集算法
- 标记-清楚算法
- 判定对象是否可以回收
- 缺点:
- 1、标记和清除效率都不高
- 2、会产生大量不连续的碎片,在分配大对象时,可能造成提前GC
- 复制算法
- 把内存分成两块,当一块使用完后,把还存活的对象复制到另一块内存上,然后直接清理这一块内存
- 标记-整理算法
- 标记之后整体往一端移动
- 分代收集算法
- 标记-清楚算法
-
垃圾收集器
CMS收集器:- 四个阶段:
- 初始标记:标记gc root 直接关联的对象
- 并发标记:标记gc root tracing的过程
- 重新标记:修正并发标记阶段,因用户线程而导致标记发生变化的的那一部分对象标记
- 并发清理:标记-清除算法
- 占用CPU: (总CPU数量+3) / 4
- 触发阈值:老年代空间占满92%
- 缺点:
- 当执行并发清理的时候,用户线程产生的垃圾无法被清楚,此时产生的垃圾为浮动垃圾,只能等到下一次full gc。
- 当执行并发清理的时候,给用户线程预留的CPU不够时,会导致处理失败,触发JVM备用方案:serial old收集器,该收集器会暂定所有用户线程
- 因为使用的是标记-清除算法,所以会产生大量的空间碎片,当无法找到足够大的连续空间去存储大对象时,会导致提前触发full gc。
ps. 默认通过整理碎片(默认开启) 和 经过多少次full gc 后进行整理(默认0次,即每次都整理) 两个参数来控制。
-
理解GC日志