本文主要介绍以下内容:
- 理论
- JVM内存模型
- 内存结构
- 内存模型
- 判定需要被回收的对象
- 方法区回收策略
- 垃圾回收算法
- JVM内存模型
- 实践
- 查看
- 优化
JVM内存模型
- 内存结构
- Java内存模型
- CPU缓存和内存的关系
- 保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的
- 禁止指令的重排序
- 内存屏障
- 阻止屏障两侧指令重排序
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
- 缓存行失效
- happens-before关系
- 具体实现:volatile、final和synchronized
- synchronized:
- 在一个线程退出同步块时,线程释放monitor对象,它的作用是把CPU缓存数据(本地缓存数据)刷新到主内存中,从而实现该线程的行为可以被其它线程看到
- 在其它线程进入到该代码块时,需要获得monitor对象,它在作用是使CPU缓存失效,从而使变量从主内存中重新加载,然后就可以看到之前线程对该变量的修改。
- final
- 正确的构造一个对象后,final字段被设置后对于其它线程是可见的
- volatile
- volatile的内存语义和sychronize获取和释放monitor的实现目的是差不多的
- volatile禁止它之前和之后的指令写入行为的重排序
- synchronized:
- 保证其它CPU的写入动作对该CPU是可见的,而且该CPU的写入动作对其它CPU也是可见的
- CPU缓存和内存的关系
- 参考博客:
判定需要被回收的对象
- 引用计数法
- 对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加一;当引用失效,计数器值减一;任何时刻计数器值都为零的对象就是不可能再被使用了
- 需要考虑很多额外情况
- 很难解决对象之间相互循环引用的问题
- 可达性分析算法
- 通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称为引用链。如果某个对象到 GC Roots 间没有任何引用链相连,则证明此对象是不可能再被使用,可以回收
- GC Roots 的对象包括:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象
- Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)
- 所有被同步锁持有的对象
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
方法区回收策略
- 方法区的垃圾收集主要回收两部分:废弃的常量和不再使用的类型
- 判定一个常量是否废弃相对简单,与对象类似,只要某个常量不再被引用,就会被清理。
- 判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了,需要同时满足下面三个条件:
- 该类的所有实例都已经被回收,即 Java 堆中不存在该类及其任何派生子类的实例
- 加载该类的类加载器已经被回收
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
垃圾回收算法
-
“Minor GC”、“Major GC”、“Full GC”
-
“标记-复制”算法、“标记-清除”算法、“标记-整理”算法
- “标记-复制”算法:
- 将新生代划分为一块较大的 Eden 区和两块较小的 Survivor 区,每次分配只使用 Eden 区和其中一块 Survivor 区。发生垃圾收集时,将 Eden 区和 Survivor 区中仍然存活的对象一次性复制到另一个 Survivor 区,然后直接清理掉 Eden 区和已经用过的 Survivor 区
- 当 Survivor 空间不足以容纳一次 Minor GC 之后存活的对象时,上一次新生代存活下来的对象直接进入老年代
- 标记 - 复制算法不适合用在对象存活率高的区域,而且会浪费一半的空间
- “标记-清除”算法
- 先标记,再回收
- 执行效率不稳定、内存空间碎片化
- “标记-整理”算法
- 在老年代中让所有存活对象都向内存空间的一侧移动,然后直接清理掉边界以外的内存
- 移动存活对象并更新其引用将会是一个极为繁重的操作,必须暂停用户应用程序线程才能进行
- “标记-复制”算法:
-
跨代引用
-
参考博客:Java 虚拟机垃圾收集机制详解