一.如何知道对象是一个垃圾?
没有任何引用指向的对象就是垃圾。
-
可达性分析(非GCRoot引用的对象)
- 通过判断对象的引用链是否可达来决定对象是否可以被回收
- 通过一系列名为GCRoot的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径就被称为引用链
- 当一个对象从GCRoot没有任何引用链相连,即从GCRoot到这个对象是不可达的,在这个时候就证明了这个对象是不可用的,可以进行回收
-
引用计数法(判断对象的引用数量来决定对象是否可以被回收)
- 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1
- 任何引用计数为0的对象实例可以被当做垃圾收集
优点:执行效率高,程序执行受影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏
二.可以作为GCRoot的对象有哪些?
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中JNI(native方法)的引用对象
- 活跃线程的引用对象
三.GC算法
- 1.标记清除(有碎片)
- 2.拷贝算法(浪费空间,一般用于MinorGC,对于新生代的)
- 3.标记整理(一边做标记一边做整理,但是效率低)
标记清除和标记整理算法一般都是针对老年代进行回收的算法
四.垃圾回收器
需要了解的相关概念:
- 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
- Stop-the-world(STW)
JVM由于要执行GC而停止了应用程序的执行
任何一种GC算法中都会发生
多数GC优化通过减少Stop-the-world发生的时间来提升程序性能 - Safepoint(安全点)
分析可达性的过程中引用关系不会发生变化的点
产生Safepoint的地方:方法调用、循环跳转、异常跳转等
安全点的数量需要适中。太少会让GC等待太长的时间,太多会增加程序运行的负荷 - JVM的运行模式
Server:启动较慢,但是启动成功之后server程序的运行效率要比Client快。采用的是重量级的虚拟机,对程序采用了更多的优化。
Client:启动较快,采用的是轻量级的虚拟机
通过java -version命令进行查看
1.分代模型
对于新生代:
- Serial(最早的垃圾回收器,现在很少用,单线程)
可以通过-XX:+UseSerialGC,使得年轻代使用该垃圾回收期进行回收
在jdk1.3之前,是Java年轻代垃圾回收器的唯一选择
单线程通过复制算法进行收集,在进行垃圾收集时,必须暂停所有的工作线程
简单高效,是Client模式下默认的年轻代收集器 - ParNew(多线程处理,工作在年轻代,专门和CMS做配合的)
可以通过-XX:+UseParNewGC,使得年轻代使用该垃圾回收期进行回收
多线程收集,其余的行为、特点和Serial收集器一样
是Server模式下首选的年轻代回收器
也是使用复制算法
单核执行效率不如Serial,在多核下执行才有优势 - Parallel Scavenge(多线程处理,工作在年轻代)
可以通过-XX:+UseParallelGC,使得年轻代使用该垃圾回收期进行回收
使用复制算法
比起关注用户线程停顿时间,更关注系统的吞吐量
在多核执行下才有优势,Server模式下默认的年轻代收集器
自适应调节策略:-XX:+UseAdaptiveSizePolicy,会把内置管理的调优任务交给虚拟机去完成
对于老年代:
- Serial Old(单线程)
可以通过-XX:+UseSerialOldGC,使得老年代使用该垃圾回收期进行回收
单线程通过标记-整理算法进行收集,在进行垃圾收集时,必须暂停所有的工作线程
简单高效,是Client模式下默认的老年代收集器 - Parallel Old(多线程处理)
可以通过-XX:+UseParallelOldGC,使得老年代使用该垃圾回收期进行回收
多线程使用标记-整理算法进行收集,JDK6之后开始提供的,吞吐量优先 - CMS
标记-清除算法
垃圾回收线程几乎能与用户线程做到同时工作,只是尽可能缩短了停顿时间
JDK5提出的第一款针对于老年代GC与工作线程并发执行的收集器
2.分区模型
- G1(1.8可用)
- ZGC
- Shenandoah
- Epsilon(无用)
3.关于JDK1.8
- 1.8默认的垃圾回收器:PS+PO
- 1.8推荐用G1
4.CMS垃圾回收器
- 中文:concurrent mark sweep
- 沿着路线在前进:内存越来越大,卡顿时间越来越短
- 垃圾回收线程和工作线程可以一起并行工作。
- 大致六个阶段,主要有四个阶段:1.初始标记;2.并发标记;3.重新标记;4.并发清理
1.初始标记:STW的时间很短,降低STW时间;G1垃圾回收器在STW的时间更短
2.并发标记:会跟工作线程发生混乱,会发生错标的问题
3.并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
4.重新标记:修正那些错标的对象,remark阶段,必须从头扫描一遍,暂停虚拟机,扫描堆中的剩余对象
5.并发清理:也是并发执行,发生错标问题,但是没关系,下次循环可以解决
6.并发重置:重置CMS收集器的数据结构,等待下一次垃圾回收
对于三色标记算法产生的漏标问题: CMS的解决方案为:incremental update
5.G1垃圾回收器
- 既用于新生代,也可以用于老年代的垃圾回收器
- 使用
复制+标记-整理算法进行回收 - 设计的目的是为了替换掉JDK5中发布的CMS垃圾回收期
- 并发+并行,使用多个CPU来缩短STW的停顿时间,与用户线程并发执行
- 分代收集,独立管理整个堆
- 空间整合,基于标记整理算法,解决了内存碎片的问题
- 可预测的停顿,能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为n毫秒的时间片段内消耗在垃圾收集上的时间不得超过n毫秒,这个n是可以设置的。
- 用到了读屏障和写屏障
- 对于三色标记算法产生的漏标问题,对应的算法为SATB
- 将整个Java堆内存划分成多个大小相等的Region区
- 年轻代和老年代不再物理隔离
5.触发fullGC的情况
- 老年代空间不足
- 永久代空间不足(jdk8之后没有了永久代,该条件不成立,取而代之的是元空间,目的是为了降低fullGC的频率,减少GC的负担,提升效率),只针对1.7
- CMS GC时出现promotionfailed,concurrent mode failure
- MinorGC晋升到老年代平均大小大于老年代的剩余空间
- 调用System.gc(),不会立即回收,而是通知jvm进行回收,回收时机需要等底层进行
- 使用RMI来进行RPC或管理的JDK的应用,每小时执行一次FullGC
五.内存模型
1.分代
1.1 新生代
-
新生代就是刚刚new出来的对象
-
新生代可以理解为大多数一次垃圾回收就能回收掉的对象
-
在新生代里面用
拷贝算法效率极高 -
年轻代分为三个区域:
Eden、2个survivor(幸存者)。比例为:8:1:1 -
不能回收的对象在两个survivor区中来回存活,直到达到
-XX:MaxTenuringThreshold设置的最大年龄则进入老年代,默认为16 -
CMS的默认的一个对象在新生代的最大年龄为16
-
新生代空间耗尽触发
MinorGC/YGC1.2 老年代
-
老年代就是经过了好多次垃圾回收之后老是回收不了的对象
-
垃圾回收不容易回收掉的对象
-
在老年代一般用
标记清除或者标记整理 -
在老年代无法继续分配空间时触发
MajorGC/FullGC,新生代老年代同时进行回收 -
大对象直接放在老年代(有参数可以确定)
六.对象回收步骤
- 当new一个对象的时候,判断是否可以不在堆上进行分配(可以在栈上进行分配,只要栈pop之后,对象就可以直接被回收,不用被GC所回收),但是这个栈上的对象有两个前提:逃逸分析(一个方法中的对象不能被另一个方法所引用)和标量替换(基础数据类型)
- 如果对象是个大对象,直接放到老年代
- 如果不是大对象,不管是不是TLAB(线程本地分配缓冲区),都会进入Eden区,然后进行GC清除,能清除的直接清除。清除不了的进入survivor1区域,年龄够了进入老年代,年龄不够进入survivor2,循环往复。