文章目录
-
-
- 一、标记算法
-
- 1、对象被判定为垃圾的标准
- 2、判定对象是否为垃圾的算法
- 3、引用计数算法
- 4、可达性分析算法
- 二、垃圾回收算法
-
- 1、标记 - 清除算法(Mark and Sweep)
- 2、复制算法(Copying)
- 3、标记 - 整理算法(Compacting)
- 4、分代收集算法(Generational Collector)
- 三、新生代垃圾收集器
-
- 1、Stop-the-World
- 2、Safepoint
- 3、JVM模式
- 4、垃圾收集器之间的联系
- 5、年轻代常见的垃圾收集器
- 四、老年代常见的垃圾收集器
-
- 1、Serial Old 收集器(-XX:+UseSerialOldGC,标记-整理算法)
- 2、Parallel Old 收集器(-XX:+UseParallelOldGC,标记-整理算法)
- 3、CMS收集器(-XX:UseConcMarkSweepGC,标记-清除算法)
- 4、G1收集器(-XX:+UseG1GC,复制+标记-整理算法)
- 5、JDK11新增的GC
- 五、常见问题
- 1、Object 的 finalize()方法的作用 是否 与C++的析构函数作用相同
- 2、Java中的强引用,软引用,弱引用,虚引用有什么用
-
一、标记算法
1、对象被判定为垃圾的标准
没有被其他对象引用。
2、判定对象是否为垃圾的算法
- 引用计数算法
- 可达性分析算法
3、引用计数算法
- 判断对象的引用数量
- 通过判断对象的引用数量来决定对象是否可以被回收
- 每个对象实例都有一个引用计数器,被引用则 +1,完成引用则 -1
- 任何引用计数为0的对象实例,可以被当作垃圾收集
-
优点
执行效率高,程序执行受影响较小。
-
缺点
无法检测出循环引用的情况,导致内存泄露
public class MyObject {
public MyObject ref;
}
public class ReferenceCounterProblem {
public static void main(String[] args) {
MyObject o1 = new MyObject();
MyObject o2 = new MyObject();
// 循环引用
o1.ref = o2; // o1 引用计数为 2
o2.ref = o1; // o2 引用计数为 2
o1 = null; // o1 引用计数为 1
o2 = null; // o2 引用计数为 1
// 如果要使 o1 引用计数变为0,需将 o2.ref=null, 而要清除 02.ref 这个引用,需要其对象的引用计数也是0,而其对象的也是1,因为 o1.ref指向了它,从而陷入了死循环。
}
}
4、可达性分析算法
通过判断对象的引用链是否可达来决定对象是否可以被回收。
从离散数学中的图论引入,程序把所有的引用关系看做一张图,通过一系列的 GC root 作为起始点开始向下搜索,搜索中走过的路径称为 引用链(Reference Chain),当一个对象没有与任何引用链相连,则该对象不可达,即会被标记为垃圾对象。
- GC Root的对象
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中的常量引用的对象
- 方法区中的类静态属性引用的对象
- 本地方法栈中 JNI(Native方法)的引用对象
- 活跃线程的引用对象
二、垃圾回收算法
1、标记 - 清除算法(Mark and Sweep)
- 标记: 从根集合进行扫描,对存活的对象进行标记
- **清除:**对堆内存从头到尾进行线性遍历,回收不可达对象内存
- 碎片化
2、复制算法(Copying)
- 分为对象面和空闲面
- 对象在对象面上创建
- 存活的对象被从对象面复制到空闲面
- 将对象面所有对象内存清除
- 特点
- 解决碎片化问题题
- 顺序分配内存,简单高效
- 适用于对象存活率低的场景
3、标记 - 整理算法(Compacting)
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收
- 特点
- 避免内存的不连续行
- 不用设置两块内存互换
- 适用于存活率高的场景
4、分代收集算法(Generational Collector)
- 垃圾回收算法的组合拳
- 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
- 目的:提高 JVM 的回收效率
- JDK6、JDK7
- JDK8+
- GC的分类
-
**Minor GC: ** Minor 区满时的垃圾回收操作。
年轻代中的垃圾回收器,年轻代是几乎所有的 java 对象出生的地方,即 java 对象申请的内存及存放都是在该区域,通常大部分对象不会存活太长的时间,采用 复制算法 进行垃圾回收。
- Full GC: 对老年代的垃圾回收,也伴随这对新生代的垃圾回收。
-
年轻代
尽可能快速地收集掉那些生命周期短的对象。
- Eden区: (伊甸园区)新生成对象所在的位置,若空间不够,会放置在Survivo区等。
- 两个Survivor区: From区 和 To 区
每次分配对象时,使用 Eden区 和 From区。当垃圾回收时,将 Eden区 和 From区中存活的对象,移动到 To区,然后清理掉 Eden区 和 From区。如果空间不够用将使用老年代中区域。
- 对象如何晋升到老年代
- 经历一定 Minor(Eden区满时的操作) 次数(默认15次)依然存活的对象, (-XX:MaxTenuringThreshold 设置进入老年代的次数)
- Survivor区中存放不下的对象
- 新生成的大对象(-XX:+PretenuerSizeThreshold)
- 常用的调优参数
- -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
- -XX:NewRatio:老年代和年轻代内存大小的比例
- -XX:MaxTenuringThreshold:对象从年轻代晋升到老生代经过GC次数的最大國值
-
老年代
存放生命周期较长的对象。采用标记-清理算法 或 标记-整理算法 进行垃圾回收。
- Full GC 和 Major GC
- Major GC 是清理老年代。
- Full GC 是清理整个堆空间—包括年轻代和老年代。
- Full GC 比 Minor GC慢,但执行频率低。
- 触发 Full GC 的条件
- 老年代空间不足
- 永久代空间不足 (JDK8以后不存在该情况)
- CMS GC时出现 promotion failed,concurrent mode failure
- Minor GC 晋升到老年代的平均大小大于老年代的剩余空间
- 调用System.gc(),请求 JVM 执行 垃圾回收,JVM 并不一定执行。
- 使用 RMI 来进行RPC 或 管理的JDK应用,每小时执行1次 Full GC
三、新生代垃圾收集器
1、Stop-the-World
- JVM 由于要执行GC 而停止了应用程序的执行,即执行 GC线程时,其他线程均处于等待状态。
- 任何一种GC算法中都会发生
- 多数 GC优化 通过减少 Stop-the-world发生的时间来提高程序性能
2、Safepoint
- 分析过程中对象引用关系不会发生变化的点(可进入执行 GC 的点)
- 产生 Safepoint的地方:方法调用;循环跳转;异常跳转等
- 安全点数量得适中
3、JVM模式
- **Server 模式:**启动慢,采用重量级虚拟机,对程序采用了更多的优化,运行稳定时速度会更快。
- Client 模式: 启动快,采用轻量级虚拟机,运行稳定时速度较 Server模式 慢。
4、垃圾收集器之间的联系
5、年轻代常见的垃圾收集器
- Serial 收集器(-XX:+UseSerialGC,复制算法)
- 单线程收集,进行垃圾收集时,必须暂停所有工作线程
- 简单高效,Client模式下默认的年轻代收集器
- ParNew 收集器(-XX:+UseParNewGC,复制算法)
- 多线程收集,其余的行为、特点和Serial收集器一样
- 单核执行效率不如Serial,在多核下执行才有优势,它默认开启的收集线程数与CPU的数量相同,在CPU非常多,可以使用 -XX:ParallelGCThreads参数来限制垃圾收集的线程数。
- Parallel Scavenge 收集器(-XX:+UseParallelGC,复制算法)
- 吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)
- 比起关注用户线程停顿时间,更关注系统的吞吐量,适合进行后台运算,不需要过多的交互运算
- 在多核下执行才有优势,Server模式下默认的年轻代收集器
- 可配合 -XX:+UseAdaptiveSizePolicy (自适应调节策略),将内存管理的调节任务交给虚拟机去完成。
四、老年代常见的垃圾收集器
1、Serial Old 收集器(-XX:+UseSerialOldGC,标记-整理算法)
- 单线程收集,进行垃圾收集时,必须暂停所有工作线程
- 简单高效,Client模式下默认的老年代收集器
2、Parallel Old 收集器(-XX:+UseParallelOldGC,标记-整理算法)
多线程,吞吐量优先
3、CMS收集器(-XX:UseConcMarkSweepGC,标记-清除算法)
- 初始标记:stop-the-world
- 并发标记:并发追溯标记,程序不会停顿
- 并发预清理:查找执行 并发标记阶段从年轻代晋升到老年代的对象
- 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象 stop-the-world
- 并发清理:清理垃圾对象,程序不会停顿
- 并发重置:重置CMS收集器的数据结构 该收集器 采用标记 - 清除算法,不会压缩存活的对象,会产生内存空间碎片化,如果要分配一个连续较大内存空间就只能触发一次GC。
4、G1收集器(-XX:+UseG1GC,复制+标记-整理算法)
- Garbage First 收集器的特点
- 并行和并发
- 分代收集(独立管理整个堆,采用不同的算法对不同区中的垃圾进行收集)
- 空间整合
- 可预测的停顿
- 将整个Java堆内存划分成多个大小相等的Region
- 年轻代和老年代不再物理离
5、JDK11新增的GC
Epsilon GC 和 ZGC
五、常见问题
1、Object 的 finalize()方法的作用 是否 与C++的析构函数作用相同
- 与C++的析构函数不同,析构函数调用时机是确定(离开其作用域),而它的是不确定的
- 当垃圾回收器寻找一个对象死亡时,至少要经过两次标记过程,如果对象在进行 可达性分析后,没有和 GC Root 相连接的引用链,就会被第一次标记,并且判断是否执行 finalize()方法,如果对象覆盖 finalize()方法且未被引用过,这个对象将放置于F-Queue队列中,并在稍后有 虚拟机自动建立的 低优先级的 finalize线程去执行触发 finalize方法。由于优先级低,不承诺其等待运行结束,即方法执行随时可能被终止。
- 方法执行随时可能会被终止
- 给予对象最后一次重生的机会
public class Finalization {
public static Finalization finalization;
@Override
protected void finalize(){
System.out.println("Finalized");
finalization = this; // 使得垃圾回收不回收此对象(不建议)
}
public static void main(String[] args) {
Finalization f = new Finalization();
System.out.println("First print: " + f);
f = null;
System.gc();
try { // 休息一段时间,让垃圾回收线程执行完成上面的立即回收
Thread.currentThread().sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("Second print: " + f);
System.out.println(f.finalization);
}
}
2、Java中的强引用,软引用,弱引用,虚引用有什么用
- 强引用(Strong Reference)
- 最普遍的引用:Object obj = new Object()
- 抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
- 通过将对象设置为null 来弱化引用,使其被回收
- 软引用(Soft Reference)
- 对象处在有用但非必须的状态
- 只有当内存空间不足时,GC会回收该引用的对象的内存
- 可以用来实现高速缓存
String str = new String("abc"); // 强引用
SoftReference<String> softRef = new SoftReference<String>(str); // 软引用
- 弱引用(Weak Reference)
- 非必须的对象,比软引用更弱一些
- GC时会被回
- 被回收的概率也不大,因为GC线程优先级比较低
- 适用于引用偶尔被使用 且不影响垃圾收集的对象
String str = new String("abc"); // 强引用
WeakReference<String> weakRef = new WeakReference<String>(str); // 弱引用
- 虚引用(PhantomReference)
- 不会决定对象的生命周期
- 任何时候都可能被垃圾收集器回收
- 跟踪对象被垃圾收集器回收的活动,起哨兵作用
- 必须和引用队列 ReferenceQueue 联合使用
String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue(); // 引用队列
PhantomReference ref = new PhantomReference(str, queue); // 虚引用
- 强引用 > 软引用 > 弱引用 > 虚引用
引用类型 被垃圾回收时间 用途 生存时间 强引用 从来不会 对象的一般状态 JVM 停止运行时终止 软引用 在内存不足时 对象缓存 内存不足时终止 弱引用 在垃圾回收时 对象缓存 GC 运行后终止 虚引用 Unknown 标记、哨兵 Unknown - 类层次结构
- 引用队列(ReferenceQueue)
- 无实际存储结构,存储逻辑依赖于内部节点之间的关系来表达
- 存储关联的且 被GC的软引用,弱引用以及虚引用
// 1
public class NormalObject {
public String name;
public NormalObject(String name){
this.name = name;
}
@Override
protected void finalize(){
System.out.println("Finalizing obj " + name);
}
}
// 2
public class NormalObjectWeakReference extends WeakReference<NormalObject> {
public String name;
public NormalObjectWeakReference(NormalObject normalObject, ReferenceQueue<NormalObject> rq) {
super(normalObject, rq);
this.name = normalObject.name;
}
@Override
protected void finalize(){
System.out.println("Finalizing NormalObjectWeakReference " + name);
}
}
// 3 Test
public class ReferenceQueueTest {
private static ReferenceQueue<NormalObject> rq = new ReferenceQueue<NormalObject>();
private static void checkQueue(){
Reference<NormalObject> ref = null;
while ((ref = (Reference<NormalObject>)rq.poll()) != null){
if (ref != null){
System.out.println("In queue: " + ((NormalObjectWeakReference)(ref)).name);
System.out.println("reference object:" + ref.get());
}
}
}
public static void main(String[] args) {
ArrayList<WeakReference<NormalObject>> weakList = new ArrayList<WeakReference<NormalObject>>();
for (int i =0; i < 3 ; i++){
weakList.add(new NormalObjectWeakReference(new NormalObject("Weak " + i),rq));
System.out.println("Created weak:" + weakList.get(i));
}
System.out.println("first time");
checkQueue();
System.gc();
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("second time");
checkQueue();
}
}