一、堆内存分配策略
对象优先在Eden区分配内存
给对象分配内存时,都会优先在Eden区分配,当Eden区满了的时候,就会发起一次Minor GC。
大对象直接分配在老年区
大对象是指需要大量连续空间的对象,这么做是为了避免给大对象分配内存因分配担保机制带来的复制而降低效率。
长期存活的对象会分配到老年区
Eden中的对象经过一次Minor GC,幸存的对象会转到Survivor区,并将对象的年龄设为1,之后在幸存者区中每经过一次Minor GC,幸存者的年龄都会加1,当对象的年龄达到年龄阈值,就会转到老年代。年龄阈值可以通过参数 -XX:MaxTenuringThreshold
来设置。
二、GC分类
针对HotSpot VM来说,GC主要是分为两大类:
- 部分收集(Partial GC)
- 新生代收集(Minor GC / Young GC):只对新生代进行回收;
- 老年代收集(Major GC / Old GC):只对老年代进行回收;
- 混合收集(Mixed GC):对整个新生代和部分老年代进行回收。
- 整堆回收(Full GC)
对整个堆和方法区进行回收。
空间分配担保
空间分配担保,是为了保证Minor GC之前老年代还有足够空间来容纳新生代中所有对象。
三、如何判断对象已经死亡?
在进行GC之前,要判断对象是否已经死亡,即该对象是否为无效对象,不会再被通过任何途径使用。
判断对象是否死亡有两种方法:
引用计数法:
给对象一个引用计数器,每有一个地方引用他时,计数器就会加1,引用失效之后就会减1,当计数器为0时,可判断该对象无效。
这个方法简单,效率高,但一般不会用,因为这个方法不能解决循环引用的问题,当两个对象互相引用对方,又无其他东西引用它们时,他们的计数器就都是1,无法回收。
可达性分析算法:
这个算法就是通过一系列称为“GC Roots”的节点,从这些节点向下搜索,节点走过的路径就是引用链,如果一个对象和GC Roots之间没有引用链连接,那该对象就是不可用的。
可作为 GC Roots 的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
四、引用
引用分为四种,分别是强引用、软引用、弱引用和虚引用,引用强度逐渐减弱。
强引用
强引用对象是不会被垃圾回收的,就算在内存不足的情况下,虚拟机宁愿抛出OOM异常,都不会回收具有强引用的对象。
软引用
如果一个对象只具有软引用,一般不会被回收,除非内存空间实在不足,才会回收软引用对象。软引用可用来实现内存敏感的高速缓存。
弱引用
如果一个对象只具有弱引用,与软引用类似,但与软引用不同的是,当该对象被垃圾回收器扫描到时,不会考虑内存空间是否充裕,会直接回收。
虚引用
如果一个对象只具有虚引用,那就相当于没有被引用,随时可以被回收,虚引用一般是用来跟踪对象被垃圾回收的活动。
弱引用和虚引用是很少用到的,软引用用得比较多,软引用可以加速JVM对垃圾内存的回收速率,可以维护系统的运行安全,防止出现内存溢出等问题。
五、运行时常量池如何回收废弃常量
假设字符串常量池中有一个字符串常量“123”,若当时没有一个String对象引用该字符串的话,那就是一个废弃常量,进行垃圾回收的时候如有必要就会把废弃变量回收。
六、方法区怎么回收废弃类
方法区中回收的主要是无用类,如何判断无用的类?需满足以下三个条件?
- 该类的实例全都被回收了,Java堆中不存在任何该类的实例
- 该类的类加载器ClassLoader也被回收了
- 该类的java.lang.class对象没有被引用,其他地方无法通过反射获取该类的方法。
当然,这只是被回收的必要条件,而不是充分条件,因为无用类不一定会被回收。
七、垃圾回收算法
标记清除算法
先标记需清除的对象,之后统一回收,这种方法效率不高,且容易产生大量不连续的碎片。
标记复制算法
将可用内存分为大小相等的两块,每次都使用其中一块,当使用的这块用完了,就把存活对象复制到另一块中,然后再把已使用的空间一次性清理掉。
标记整理算法
先标记存活的对象,把这些对象往一端移动,然后清除端边界以外的内存。
分代收集算法
分代收集算法其实就是根据新生代和老年代的特性选择合适的算法。
新生代:新生代有很多对象死亡,所以用标记复制算法好,每次只需少量对象的复制成本即可完成垃圾收集。
老年代:老年代中存活的对象较多,用标记清除或者标记整理算法比较好。
八、垃圾收集器
垃圾收集器是内存回收的具体实现
Serial 收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器配合工作。
Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用标记-复制算法,老年代采用标记-整理算法。
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。