在前文JVM运行时数据区 说过,JVM对堆内存采用分代策略。
一、内存空间
- 新生代:新生代的绝大部分对象有朝生熄灭的特点,存活率很低,回收效率高
- 老年代:老年代的对象由新生代中存活多次、或者特殊原因(后面介绍)从新生代直接移来的。老年代的对象生命周期较长,垃圾回收后对象存活率较高,但回收效率很慢
- 元数据区:
二、垃圾回收
对象存活判断
- 引用计数法
给对象加一个引用计数器,当对象被引用的时候,引用计数器加1,当引用失效的时候引用计数器减1,当引用计数器为0,说明当前对象没有被引用,需要回收
引用计数法有一个问题,如果对象循环调用,A调用B,B调用A,AB对象相互引用,那么AB对象的引用计数器的值永远都不会为0,AB就永远都不会被回收,直接造成内存泄漏问题。 - 可达性分析法
以GC ROOT为root节点的树形结构,所有不再该树上的对象,都会被回收。
A、B对象相互引用,但是从GC ROOT节点不可达,所以A、B对象会被回收。
那么那些对象可以作为GC ROOT对象:
可作为GC ROOT的对象
不被GC回收的对象都可以作为GC ROOT对象
- 虚拟机栈中引用的对象,比如:方法中的Object对象
- 方法区中的静态引用的对象,比如:static 修饰的对象
- 方法区中静态常量的对象,比如final和static修饰的对象
- 本地方法栈中JNI引用的对象,JNI也就是调的native方法
但是如果一个对象实现了finalize()方法,对象不会立即被回收。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
对象引用
以上不论是通过哪种方式判断对象是否存活,都和引用有关,那么java中的引用划分:
- 强引用:Java中大部分都是强引用,只要对象被强引用,就不会被回收;即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
- 软引用:软引用是用来描述一些有用但并不是必需的对象,对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。比如Java中的java.lang.ref.SoftReference类
- 弱引用:弱引用的对象只有GC,就会回收该对象,不论内存是否充足。比如java中的TheradLocal的key就是弱引用
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
- 虚引用(幽灵引用或幻影引用):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。与弱引用区别:在GC时会被通知。
垃圾回收方式
- 标记-清除
标记-清除收集器,会产生垃圾碎片 - 复制
复制算法效率高,但是很浪费空间 - 标记-整理
该算法标记阶段和标记-清除一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。 - 分代收集
堆内存分为青年代、老年代、方法区。按照不同的特性
- 青年代的特点是朝生夕死,每次有大量数据被回收,所以采用复制收集器;(minor GC)
- 老年代的特点是每次回收少量数据,所以采用标记-整理收集器(major GC)
- 方法区的常量池也会被垃圾回收,采用标记-整理收集器