这篇文章通过翻译、总结,提炼SUN公司(已被甲骨文收购) 的文档 "Memory Management in the Java HotSpot™ Virtual Machine", Sun Microsystems, April 2006"
得到的JVM内存管理方面的学习笔记。其中大部分插图来自文档。 原官方文档 查阅原文
1.内存的手动、自动管理
内存的手动管理,需要明确地指出对象何时不再被使用并将其释放,而自动管理,需要根据对象的引用情况来判断释放与否。当然,被引用的对象不一定都有用;混乱的引用导致了一些对象不会被释放。自动管理解决了大部分内存问题,但无法完全解决内存问题。
2.垃圾回收的概念
垃圾回收器(Garbage Collector, GC) 是JVM 进行内存管理的工具。
垃圾回收器负责:
Java程序中被引用的对象(应该指被有效引用的对象)称为存活的(live). 不再被引用的对象称为死亡的(dead) 也称作垃圾(garbage). 对死亡的对象进行查找并释放内存空间的过程被称为垃圾回收。
2.1.什么是良好的垃圾回收器?
安全即有用的数据不会被回收,无用的数据不会长期得不到释放;高效即回收器工作时不会引起长时间的卡顿。
然而,大多数系统都有时间,空间和频率之间的权衡。堆小会提升回收速度,但堆很快会被填满,造成更频繁地回收。
在对象的被释放后,回收得到的空间可能是分散在不同区块的碎片,这些碎片可能都没有足够的空间来分配一个大对象。我们需要消除碎片获得连续的空间,消除碎片的方法成为“压实”(compaction)。
分配与回收是否成为多线程应用程序在多处理器系统上扩展的瓶颈。
2.2.垃圾回收算法要权衡什么?
设计或选择一个垃圾回收算法应做出如下选择:
串行则只有一个CPU进行回收,并行则将回收任务被分割交由不同的CPU同时执行。并行虽迅速,但会产生额外的复杂性与潜在的碎片。
“暂停”则回收执行时,应用程序的将完全暂停执行。同步则通过增大开销来缩短暂停时间以改善性能,同步可与应用程序同时进行,提高响应速度,但面对对象更新时要格外小心。
“压实”则在回收工作后将所有存活的移动至一起,完全回收剩余内存。之后将指针指向第一个自由位置,以便快速分配对象,之后更新自由位置用于下一次分配。“非密实”在对象释放后不进行处理,以快速完成回收任务,因此会产生潜在碎片。复制可以将存活的对象“疏散”到不同的内存区域,原区可以被快速利用进行下一次分配,但其需要额外的时间和空间。
3.从J2SE 5.0 HotSpot JVM起的四种垃圾回收器
3.1.HotSpot JMM概况
HotSpot JMM 存在三个区块:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation), 大部分对象在初始化后被分配到年轻代的Eden Space, 当Eden Space 将饱和时,通过Survivor Space 进行回收,多次回收后幸存的对象会被移动到年老代上的养老区(Tenured Space). 经历多次年轻代垃圾回收后的幸存对象被分配到年老代。空间占用大的对象可以直接分配进入年老代。持久代一般存储可被垃圾回收器直接识别为持久的对象,例如类和方法以及描述类。
3.2.垃圾回收的类型
年轻代将饱和时,执行年轻代回收(Young Generation Collection), 亦称小回收(Minor Collection). 年老代或持久代将饱和时,执行全面回收(Full Collection),亦称主回收(Major Collection) 对所有代对象的进行回收。通常,年轻代的对象先被回收,为保障高效,年轻代回收使用特定的算法。而年老代和持久代则使用相同的算法。
3.3.Serial Collector (SC 串行回收器)
SC由单个线程负责执行,回收时应用程序的执行将被暂停。
回收执行时,在年轻代的Eden Space 中存活的对象被复制到Survivor Spaces 的To Space 中,但如果该对象大小超过To Space 的空间,它将被直接复制到年老代。Survivor Spaces 中From Space 存活的对象仍然被认为是年轻的,因此也会被复制到To Space 中,直到对象被标记为年老时才会被复制到年老代。当To Space 快满时,Eden Space 和From Space 中的幸存对象不会被复制进去,无论经历了多少年轻代的垃圾回收。当Eden Space 和From Space 中幸存对象全被复制后,遗留对象(图中标记为红色X)全部被标记为死亡,不再被检查与标记。年轻代回收完毕后,Eden Space 和From Space 将清空;如有没进入年老代的对象,那它只能在To Space 中,此时From Space 和To Space 交换角色。
SC在年老带中采用mark-sweep-compact算法进行垃圾回收。
(1) 标记(mark):判断哪些对象仍然存活并进行标记
(2) 扫描(sweeps):通过对若干代的扫描确定垃圾
(3) “滑动—压实”(sliding-compaction):对幸存对象进行压缩,以使他们在连续的空间上有序并至地排列。“压实”操作后,碎片被整合集中,使新进入的对象可以使用凹凸指针(bump-the-pointer)进行快速地址分配。
多用于客户机(client-style). 程序可以容忍短暂地暂停。在J2SE 5.0之后的版本中默认开启。
指令配置:-XX:+UseSerialGC
3.4.Parallel Collector (PC 并行回收器)
PC是高吞吐量回收器(Throughput Collector), 具有充分利用核心的优势。当前应用广泛。PC是SC的加强版本。
PC与SC采用相同的方式对年轻代进行垃圾回收,仍是一个“‘暂停—复制’回收器”,但并行降低了时间开销,提高了应用的吞吐量。
与SC相同,采用mark-sweep-compact回收算法提升效率,但仍有罕见情况使年老代的回收造成程序较长时间的暂停。
多用于服务器(server-class). 在J2SE 5.0之后的版本中默认开启。
指令配置:-XX:+UseParallelGC
3.5.Parallel Compacting Collector (PCC 并行压缩回收器)
与PC相同,PCC在年老代垃圾回收采用了新算法,并最终取代PC。
PCC与PC采用相同的方式对年轻代进行垃圾回收。
PCC的年老带回收会造成应用暂停。
(1) 标记(mark):在逻辑层面上将年老代划分成大小固定的分区。通过多线程先标记根集合中可以直接访问的对象,再标记剩余对象。只要区中有幸存对象,该区的大小和位置信息将会被采集更新。
(2) 总结(summary):该方法是对区进行操作,而非操作对象。通常情况下,当进行年老代垃圾回收时,由于前某一代的“压实”操作,使得当前内存空间中,左边的存储密度仍然很大,甚至左边的对象几乎是连续排列的。从高密度区中回收零星的碎片空间可能不值得再次“压实”造成的开销。所以,总结会从最左端检查区的密度(依据标记后区的信息采集结果), 直到到达一个点,从该点到最右端进行“压实”回收内部的碎片空间可以弥补开销。这个点被成为“密度前缀”(dense prefix), 它左边的对象均不会被位移而右边的空间均会被压缩回收。
总结方法实际是串行实现的,当然也可以并行,但并没有标记和“压实”的并行实现那样重要。
(3) “压实”(compaction):多个线程基于总结操作的结果,确定需要被填满的区,然后独立复制数据进行填充。最后,堆的内存分配是一端十分密集,另一端几乎为空。
多线程的机器,但不适合大型共享机器。
应用前需指明。
指令配置:-XX:+UseParallelOldGC
3.6.Concurrent Mark-Sweep (CMS) Collector (CMSC 并发标记回收器)
在很多应用场景中,快速响应(fast response time) 比端到端的吞吐量(end-to-end throughput) 更重要。如果使用了大堆,年老代的回收可能造成长时间停顿。
HotSpot JVM 引入了CMSC 亦作“低延迟回收器”(low-latency collector) 来解决这一问题。
CMSC与PC采用相同的方式对年轻代进行垃圾回收。
大多数的CMSC垃圾回收是在应用程序执行时完成的。
(1) 初始标记(initial mark):应用暂停,标记根集合中可以直接访问的对象,获得幸存对象的初始集合。
(2) 并发标记(concurrent mark):标记初始集合中全部被间接引用的对象。
(3) 备注/再标记(remark):由于并发标记不会暂停应用,无法保证被更新的引用都得到标记。为此,备注过程再次暂停应用,对并发标记时被修改的对象进行标记,以保障垃圾回收的完整性。由于备注所需的暂停时长远大于初始标记的暂停时长,采用多线程来提高效率。
(4) 扫描(sweep):CMSC加大备注过程的开销来减少暂停时长,并且是唯一没有“压实”操作的回收器,即不会将幸存的对象有序并至排列。
虽然非“压实”节约时间,但由于空闲内存不连续,回收器无法使用凹凸指针技术分配新对象。为此,CMSC用多个链式队列(应该是对碎片依据空间大小分类存放)碎片,根据新对象的大小通过链式队列分配适当位置。因此,相比于其他回收器,CMSC在年老代的资源分配成本更高,也需要开辟更大的堆。
另外,尽管老年代的每次回收对全部对象进行清洗,但之后的“新生垃圾”,亦作浮动垃圾(floating garbage), 只能在下一次老年代回收时被清理。由于没有“压实”,碎片迟早会积累,为此CMSC对活跃对象进行跟踪(track), 通过估算空间需求来分割或合并碎片空间。
CMSC适用于要求暂停时间短,并且可以承担与回收器共享处理器资源的应用。通常适用于多线程,老年代数据集较大的应用,如网络服务器。
应用前需指明。
指令配置:-XX:+UseConcMarkSweepGC
-XX:+CMSIncrementalMode (incremental mode)