《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略
第三章 垃圾收集器与内存分配策略
3.1 概述
![]()
![]()
- 哪些内存需要回收
- 何时回收
- 如何回收
- 虚拟机栈(栈帧中的本地变量表)引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)引用的对象
- 该类所有的实例都已经回收,也就是java堆中不存在该类的实例。
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。
1. 新生代(Young Generation):也有叫做年轻代的,这里使用《深入理解JAVA虚拟机》中的叫法,下同。 其实看名称就能看出一些,一般情况下,新创建的对象都会存放到新生代中(大对象除外)。 新生代中对象的特点是:很快就会被GC回收掉的或者不是特别大的对象。 为了方便垃圾收集,新生代又分出了一个Eden区,两个 Survivor区。 JVM 每次只会使用 Eden区 和其中的一块 Survivor 区域来为对象服务,另一块Survivor区域是空的,用于垃圾回收。 举个例子,第一次回收的时候,虚拟机会将 Eden区+Survivor(from)区域的存活对象复制到Survivor(to)上(存活对象小于Survivor(to)的空间),清空Survivor(from),虚拟机使用Eden区+Survivor(to); 第二次回收的时候,虚拟机再将Eden区+Survivor(to)存活的对象复制到Survivor(from)。 这三个区域默认情况下是按照8:1:1分配,也可以手动配置。 2. 老年代(Old Generation):在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代(默认是15,可以通过-XX:MaxTenuringThreshold来设置)。 另外,比较大的对象也会进入老年代,可以-XX:PretenureSizeThreshold进行设置。 如-XX:PretenureSizeThreshold3M,那么大于3M的对象就会直接就进入老年代。 因此,老年代中存放的都是一些生命周期较长的对象或者特别大的对象。 3. 永久代(Permanent Generation ):即JVM的方法区。在这里存放着一些被虚拟机加载的类信息(别忘了还有动态生成的类)的静态文件,这就导致了这个区中的东西比老年代和新生代更不容易回收。 永久代大小通过-XX:MaxPermSize=3.3.1 标记-清除算法 首先标记所有需要回收的对象,在标记统一完成后回收所有标记的对象。 不足:1 标记和回收的效率都不高 2标记清除后会产生大量不连续的内存碎片,碎片太多可能会导致以后再重新运行时需要分配较大对象后,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。 3.3.2 复制算法 它将可用的内存按容量分为大小相等的两块,每次只使用其中的一块。当这块内存用完时,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 一块较大的Eden 和两块较小的Survivir。分配担保。 3.3.3标记-整理算法 同标记清除算法,但是在清除时让所有存活的对象都向一端移动,然后清理掉边界以外的内存。 3.3.4分代收集算法 根据对象存活周期不同将内存划分为几块,一般是把java堆分为新生代和老年代。在新生代中,每次垃圾收集时都发生大批对象死去,就选用复制算法。在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理 或标记-整理算法。 3.4 HotSpot的算法实现 3.4.1 枚举根节点 GC进行时必须停顿所有java执行线程,这件事被称为“Stop the World”。 3.4.2安全点 程序执行时,只有在安全点才能暂停开始GC。以及如何让线程都跑到安全点再停顿。分为抢占式中断和主动式中断。 1.抢占式。现在基本没有使用抢占式。 所有线程停止,如果没到安全点则继续执行到安全点。 2.主动式。设置一个标记,各个线程执行时主动轮询这个标记,发现中断标记为真时就自己中断挂起,轮询标记的地方和安全点是重合的。 3.4.3 安全区域 安全点的扩充。安全区域是指一段代码中,引用关系不会发生改变,区域中任何一点开始GC都是安全的。 3.5 垃圾收集器 举例+ CG日志分析 3.6 内存分配与回收机制 对象的内存分配,往大了说就是在堆上分配,对象主要分配在新生区Eden区上,如果启动了本地线程分配缓存,将按线程优先在TLAB上分配。少数情况下可能会直接分配到老年代中。 3.6.1对象优先在Eden分配 大多数情况下,对象在Eden区中分配,当Eden区中没有足够空间进行分配时,虚拟机会发生一次Minor GC. 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也较快。 老年代GC(Major GC/Full GC):指发生在老年代的GC,出现Major GC,经常会伴随最少一次的minor gc。一般比前者速度慢10倍以上 3.6.2 大对象直接进入老年代 所谓大对象是指,需要大量连续内存空间的java对象,最典型大对象就是很长的字符串以及数组。目的:避免大量复制回收内存。有些回收器可以设置大对象最大内存上限标准。 3.6.3长期存活的对象将进入老年代 每一个对象定义了一个对象年龄计数器,对象在Eden区域出生并且经历一次minor GC存活,且能被survivor 容纳,则年龄为1,以后每熬过一次minor GC,年龄加一,超过一定值(默认15)会被移至老年代。 3.6.4 动态对象年龄判断 如果在survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代,无需等待MaxTenuringThreshold中的要求。 3.6.5分配担保 如果分配担保失败,进行一次Full GC。进行设置。 4. 元空间(Metaspace):从JDK 8开始,Java开始使用元空间取代永久代,元空间并不在虚拟机中,而是直接使用本地内存。 那么,默认情况下,元空间的大小仅受本地内存限制。当然,也可以对元空间的大小手动的配置。