JVM---GC
概述
/**
* 【GC---概述】
* <什么是垃圾>
* 在运行程序中 没有任何指针指向的 对象;
*
* <为什么要进行GC>
* a, 内存消耗完
* b, 碎片整理
* 除了回收垃圾对象外,GC也可以清理内存的记录碎片,碎片整理将整理出的内存分配给新的对象;
* c, 影响程序正常运行
* 随着应用程序业务越复杂,用户越来越多,没有GC就不能保证应用程序的正常运行;
* 经常造成的STW的GC跟不上实际的需求,需要对GC进行优化;
*
* <早期的GC>
* 在早期的C/C++时代,GC基本是是手工进行;
* 开发人员可以使用new申请内存,delete释放内存;
*
* 特点:
* 1、灵活控制内存
* 2、给开发人员带来 频繁申请和释放内存的管理负担
* 若有一处内存区由于开发人员忘记,就会产生内存泄漏,垃圾对象永远无法清除;
*
* <Java垃圾回收机制>
* 自动内存管理,无需开发人员手动参与内存的分配与回收;
*
* 特点:
* 1、降低内存泄漏、内存溢出的风险;
* 2、开发人员从繁重的内存管理释放出来,专注于业务开发
* 3、弱化开发人员在程序出现异常时定位、解决问题的能力;
*
* 垃圾回收器 对堆(回收重点)、方法区进行回收;
*/
标记阶段
/**
* 【GC---垃圾回收-标记阶段】
*
* <标记阶段做什么?>
* 堆中存放几乎所有的Java对象实例,在GC之前,首先需要判断内存中哪些是存活对象,哪些是死亡对象;
* (只有被标记为死亡对象,GC在执行垃圾回收时,才会释放其占用的内存空间)
*
* <如何判断一个对象已经死亡?>
* 当一个对象 不被任何其他对象引用;
*
* <标记阶段算法>
* 1、引用计数算法
* 对每个对象保存一个整型的 引用计数器属性;
*
* 引用计数器:
* 作用:
* 用于记录对象被引用的情况;
*
* 对于一个对象A,只有任何一个对象引用A,则A的计数器+1;
* 当引用失效时,A的计数器-1;
* 当A的计数器值为0,表示A不再被使用;
*
* 优点:
* 实现简单,垃圾对象易于标识;
*
* 缺点:
* 需要单独的字段存储计数器,增加内存空间的开销;
* 每次赋值都要更新计数器,增加了时间开销;
* 无法处理循环引用的问题(导致Java的垃圾回收没有选择引用计数算法);
* eg:
* p -> A <-> B
* rc=2 rc=1
*
* 当P引用移除后,导致内存泄漏
* A <-> B
* rc=1 rc=1
*
* 2、可达性分析(根搜索、追踪性垃圾收集)算法
*
* GC Roots:
* 一组必须活跃的引用;
*
* Java中,GC Roots包括哪些元素:
* 虚拟机栈内
* 参数,局部变量等;
* 本地方法栈内
* static变量
* ...
*
* 引用链:
* 可达性分析算法,内存中存活对象 会被 GC Roots直接或间接连接着,搜索所走过的路径称为 引用链;
*
* 基本思路:
* 以GC Roots为起始点,按照从上至下的方式搜索 被GC Roots所连接的目标对象是否可达;
* 如果目标对象没有任何 引用链 相连,则不可达,意味着目标对象死亡,可标记为垃圾;
*
* 注意:
* 当使用 可达性分析算法 判断内存是否可回收时,分析工作必须在 一个能保障一致性的快照中 进行;
* GC时 必须Stop The World就是这个原因;
*/

对象的finalization机制
/**
* 【GC---对象的finalization机制】
* Java语言 提供了 对象终止(finalization)机制 允许开发人员 提供 对象被销毁之前的自定义处理逻辑;
*
* finalize():
* java.lang.Object#finalize()
*
* Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. 当某个对象为垃圾时,垃圾收集器会调用该对象的finalize();
* A subclass overrides the finalize method to dispose of system resources or to perform other cleanup. 子类可以重写finalize() ,用来处理系统资源或执行其他清理;
*
* ***只会被 垃圾收集器 调用一次;
*
* 注意:
* 永远不要显式调用某个对象的finalize(),应该交给 垃圾收集器去调用,理由:
* 1、可能导致对象复活
* 2、执行时间没有保障,完全由GC线程决定;
* 3、
*
* 由于finalize()的存在,虚拟机中的对象一般处于3种可能状态:
* 可触及:
* 从根节点开始,可以到达这个对象;
* 可复活:
* 从根节点开始,所有引用都被释放,但是有可能在finalize()中复活;
* 不可触及:
* 对象的finalize()被调用 且 没有复活;
*
* 判断一个对象是否可回收,至少经历2次标记过程:
* 1、如果对象A到GC Roots不可达,标记第一次;
* 2、判断对象A是否有必要执行finalize():
* a, 如果对象A没有重写finalize() 或 finalize()已被调用,对象A 为不可触及;
* b, 如果对象A重写finalize() 且 未被调用,对象A会被插入到 队列中,由低优先级的Finalizer线程 调用对象A的finalize();
* 如果调用finalize()后,对象A可达,则被移出 被回收 集合;
*/
public class FinalizeTest {
static Object o;
@Override
protected void finalize() throws Throwable {
System.out.println("sub finalize...");
o = this;
}
public static void main(String[] args) throws InterruptedException {
o = new FinalizeTest();
o = null;
System.gc();
Thread.sleep(10000);
if (null == o){
System.out.println("o is dead");
}
else {
System.out.println("o is still alive");
}
}
}
使用jprofiler查看GC Roots、分析OOM
/**
* 【GC---使用jprofiler查看GC Roots、分析OOM】
* jprofiler
* Idea插件
*
* dump文件
* what
* 进程的内存镜像;
*/
清除阶段
/**
* 【GC---垃圾回收-清除阶段】
* 当成功标记出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放死亡对象所占用的内存空间,以便有足够的可用空间为新对象分配内存;
*
* <清除阶段算法>
* 1、标记-清除算法 Mark-Sweep
* 实现思路:
* 当堆中有效内存空间被耗尽时,会STW,然后进行2个操作:
* 标记:
* 垃圾回收器 从引用根节点开始遍历,标记 所有被引用的对象(在对象的Header中记录为可达对象);
* 清除:
* 垃圾回收器 对堆内存 从头到尾 进行线性遍历,如果发现某个对象在其Header中没有标记为可达对象,则进行回收;
*
* 缺点:
* 效率不高;
* GC的时候,需要STW,导致用户体验很差;
* 这种方式清理出来的内存是不连续的,容易产生碎片,需要维护一个空闲列表;
*
* 注意:
* 何为清除?
* 清除并不是真的置空,而是把需要清除的对象地址保存在空闲列表中;
* 当有新对象需要加载时,判断垃圾位置空间是否够,如果够,就覆盖垃圾空间内容;
*
* 2、复制算法 Copying
* 实现思路:
* 将内存空间分为2块,每次只使用其中一块;
* 在GC时,将 可达对象 复制到 另一个块内存中,然后清除正在使用的内存;
*
* 优点:
* 没有标记、清除过程,实现简单,运行高效;
* 复制后的内存空间连续,不会出现碎片问题;
*
* 缺点:
* 需要2倍的内存空间;
* 复制算法需要维护引用关系,时间开销也不小;
* 适用于 可达对象比较少的场景;
*
* 场景:
* 新生代的 Survivor区
*
*
* 3、标记-压缩(整理)算法 Mark-Compact
* 实现思路:
* 标记:
* 垃圾回收器 从引用根节点开始遍历,标记 所有被引用的对象(在对象的Header中记录为可达对象);
* 压缩:
* 移动所有可达对象 且 按内存地址依次排列,然后,将末端内存地址以后的内存全部回收,无需使用空闲列表;
*
* 优点:
* 解决 标记-清除算法 的碎片化问题;
* 解决 复制算法 的内存减半的问题;
*
* 缺点:
* 移动对象同时,需要维护引用关系;
* 移动过程中,需要STW;
*
* 本质:
* 标记-清除算法执行后,再进行一次内存碎片整理;
*
* 标记-清除 与 标记-压缩 的区别:
* 标记-清除 是非移动式的回收算法;
* 标记-压缩 是移动式的回收算法;
*/
标记-清除算法

复制算法

标记-压缩算法

浙公网安备 33010602011771号