JVM:垃圾收集GC要完成的三件事
垃圾收集需要完成的三件事:
- 哪些内存需要回收
- 什么时候回收
- 如何回收
哪些内存需要回收
Java堆、方法区
程序计数器、本地方法栈、虚拟机栈随线程而生、随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的。
因此程序计数器、本地方法栈、虚拟机栈不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟着回收了。
什么时候回收
Java堆:对象已死
方法区:废弃的常量、不再使用的类
如何判断对象已死?
引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就时不可能再被使用的。
这个看似简单的算法有很多例外的情况要考虑,必须要配合大量额外处理才能保证正确地工作,比如对象之间相互循环引用的问题。
可达性分析算法
可作GC Roots的对象
- 在虚拟机栈中引用的对象(比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等)
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- Java虚拟机内部的引用,如基本数据对应的Class对象,一些常驻的异常对象、系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
真正宣告一个对象死亡,至少经历两次标记过程:
- 可达性分析,没有发现与GC Roots相连接的引用链。第一次被标记
- 筛选,此对象是否有必要执行finalize()方法,如果判定为有必要,则该对象会被放置在一个F-Queue的队列之中,并在稍后被Finalizer线程执行它们的finalize()方法。收集器将对F-Queue中的对象进行第二次小规模的标记。
回收方法区
回收废弃常量与回收Java堆的对象非常类似
(废弃的常量:没有任何字符串对象引用常量池中的常量,且虚拟机中也没有其他地方引用这个字面量)。
不再使用的类:
- 该类所有的实例都已经被回收
- 加载该类的类加载器已经被回收
- 该类对应的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
如何回收(垃圾收集算法)
分代收集理论
一般至少会把Java堆分为新生代和老年代两个区域。
弱分代假说:绝大多数对象都是朝生夕灭的
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
部分收集(Partial GC):
- 新生代收集(Minor GC/Young GC)
- 老年代收集(Major GC/Old GC)
- 混合收集(Mixed GC)
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
算法 | 图例 | 缺点 | |
---|---|---|---|
标记-清除 | 1、执行效率不稳定(标记、清除两个过程随对象数量增长而降低) 2、内存空间碎片化 | ||
标记-复制 | 空间浪费(内存缩小为原来的一半) | 常用于回收新生代 | |
标记-整理 | 移动存活对象造成的停顿 |