写作阅读笔记,实际上笔记本体是思维导图,而本人的思维导图大概只算得上是原始文档的分句版本,所以感觉这笔记……可能和照抄翻译差别不大(。)
| 本篇涉及章节: | 2 自适应调优 | 3 代 | 4 确定“代”的大小 |
|---|---|---|---|
| 2 Ergonomics | 3 Generations | 4 Sizing the Generations |
其中《确定“代”的大小》在SE23中,名为《影响垃圾回收性能的因素》
本笔记所阅读的官方文档为 JDK8 和 JDK23 的同名章节《虚拟机垃圾回收调优指南》。版本不同的部分本文会有引用标记。
垃圾回收器对应用程序使用对象的方式作出假设,并反映在可调参数中,可调整参数以提高性能,而不会牺牲抽象能力。当无法再从正在运行的程序中的任何指针访问某个对象时,该对象被视为垃圾对象。最直接的垃圾回收算法会迭代掉每个可访问的对象。任何剩余对象都被视为垃圾,此方法所需的时间与活动对象的数量成正比。虚拟机的多种垃圾回收算法使用分代回收(generational collection)组合使用。
自适应调优 Ergonomics
是 JVM 和垃圾回收调优(例如基于行为的调优)提高应用性能的过程。JVM 会为不同的平台提供相应的默认垃圾回收器、堆大小和运行时编译器。基于行为的优化会动态调整堆的大小,以满足指定行为。
垃圾回收器、堆、和运行时编译器默认选择
插播【服务器】的定义:
- 2 个或以上的多处理器
- 2G 或以上的物理内存
当环境符合服务器的定义时,会启动以下默认选择:
-
吞吐量垃圾回收器 (Throughput garbage collector)
-
初始堆大小为物理内存1/64,最大1G (Initial heap size of 1/64 of physical memory up to 1 GB)
-
最大堆大小为物理内存 的1/4,最大1G (Maximum heap size of 1/4 of physical memory up to 1 GB)
-
服务器运行时编译器
64位系统的初始堆大小和最大堆大小 JDK8-虚拟机垃圾回收调优指南-6-并行(吞吐量)收集器(Parallel Collector)
这个下一篇笔记也会有提到
基于行为的优化
对于并行收集器,Java SE 提供了以下两个垃圾收集调整参数,均基于实现应用程序指定行为的目标。
1. 最大暂停时间目标 Maximum Pause Time Goal
是垃圾回收器停止应用程序并恢复不再使用的空间的持续时间。
作用:限制这些暂停中的最长暂停
命令行: -XX:MaxGCPauseMillis=<nnn>
-
向垃圾回收器提示需要 nnn 毫秒或更短的暂停时间
-
垃圾回收器会据此调整堆大小和与垃圾回收相关的其他参数,以使回收暂停时间短于 nnn 秒
平均暂停时间和该平均值的差异由垃圾回收器维护。平均值从执行时开始获取,但经过了加权,以便最近的暂停计数有更高的权重。
当 暂停时间的平均值+方差>最大暂停时间目标 ,垃圾回收器会认为其 未达到目标
但 默认是没有最大暂停时间目标的 ,因为可能会导致垃圾回收器更频繁地发生,进而降低应用程序的整体吞吐量。垃圾回收器会尝试满足吞吐量目标之前的任何暂停时间目标。但是某些情况下无法实现所需的暂停时间目标。
2. 吞吐量目标 Throughput Goal
是指用户线程运行时间占总时间的比例,包括用于内存分配的时间(但通常不需要为了加快分配速度而进行调优)。
命令行:-XX:GCTimeRatio=<nnn>
垃圾回收时间与应用程序时间的比率为 1/(1+<nnn>)。例如:-XX:GCTimeRatio=19 将垃圾回收总时间的 1/20(5%)作为目标。
垃圾回收所花费的时间是新生代和年老代回收的时间总和。如果未达到吞吐量目标,则增加各代的大小,以增加应用程序在集合之间可以运行的时间。
优化策略 Tuning Strategy
-
不要为堆设置最大值,除非直到需要的堆大于默认最大堆大小。
- 选择足以满足应用程序的吞吐量目标
-
堆会增长或收缩到支持所选吞吐量目标的大小。
- 应用程序行为的更改可能会导致堆增长或收缩。例如,如果应用程序开始以更高的速率进行分配,则堆将增长以保持相同的吞吐量。如果堆增长到其最大大小,但未达到吞吐量目标,则最大堆大小对于堆吞吐量目标来说太小。应该把最大堆大小设置为接近平台上物理内存、但又不会导致应用程序交换的值。
-
如果仍未达到吞吐量目标,说明应用程序时间目标对于平台上的可用内存来说太高。
- 如果可以达到吞吐量目标,但存在太长的暂停,那么需要设置最大暂停时间目标,但设置这个目标也有可能意味着无法实现吞吐量目标,谨慎考虑,折衷选择
-
通常,当垃圾回收器试图满足竞争目标时,堆的大小会波动,即使应用程序已达到稳定状态也会波动。吞吐量目标(需要更大的堆)与最大暂停时间和最小占用空间(两者都可能需要一个小堆)的目标互斥。
代 Generations
虚拟机的多种垃圾回收算法使用分代回收(generational collection)组合使用。简单垃圾回收检查堆中的每个活动对象。虽然简单的垃圾回收会检查堆中的每个存活对象,但分代垃圾回收利用了大多数应用程序中观察到的几个经验性特性,以尽量减少回收未使用(垃圾)对象所需的工作量。
弱 “代” 假设:大多数对象只能存活很短的时间。
通常有一些在初始化时分配的对象在进程退出前一直存在。除此之外是某些中间计算期间存在的对象,大部分对象在“仍然年轻”的时候就会死亡,针对这一特性进行收集,效率较高。
基于以上统计结果,内存区分了不同的 “年代” 。内存池也包含有不同年龄的对象,当某个“代”已饱和,就会发生垃圾回收。
绝大多数对象都分配在“年轻代”的池子中,且在那里消亡。当“年轻代”饱和时,会触发次要收集(minor GC)来专做年轻代的垃圾回收,此操作不会影响其他“年代”。
-
minor GC 可以进一步在“弱代假设”成立的基础上回收几乎所有的年轻代对象。
- 此类回收成本与被回收的存货对象数量成正比。饱和死亡的年轻代会被很快回收。
-
在每次 minor GC 期间,“年轻代”中有一部分会移动到“年老代”。最终,“永久代”饱和必须被回收,从而导致一个主要回收(major GC),会回收整个堆。
- major GC 的持续时间通常比 minor GC 要长得多,因为涉及的对象数量多
垃圾回收器与适用场景
-
串行垃圾回收器:数据集较小的应用程序,默认参数已够用
-
并行或吞吐量垃圾回收器:具有中到大型数据集的应用程序
代的默认排列方式
官方文档原图
代分区
- 年轻代 young
- eden:大多数对象的最初分配空间
- 两个survivor幸存者空间:可空,可作为 eden 中任何活动对象的目的地、下一个复制集合期间的目标
- 终身代 tenured
性能注意事项
前述的暂停时间和吞吐量是两个度量标准。最好使用特定于应用程序的指标来衡量。
- 例如,可以使用客户端负载生成器测试 Web 服务器的吞吐量,而可以使用 pmap 命令在 Solaris 操作系统上测量服务器的占用空间。但是,通过检查虚拟机本身的诊断输出,可以很容易地估计由于垃圾回收而导致的暂停。
足迹(Footprint)是指进程的工作集,以页面和缓存行来衡量。在物理内存有限或进程众多的系统中,足迹可能会影响系统的可扩展性。及时性(Promptness)是指从对象变为无效状态到内存重新变得可用之间的时间,这对于包括远程方法调用(RMI)在内的分布式系统来说是一个重要的考虑因素。
命令行选项 -verbose:gc
会在每次回收时打印有关堆和垃圾回收的信息。
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
输出显示两个次要集合,后跟一个主要集合。箭头前后的数字(例如,第一行中的 325407K->83000K)分别表示垃圾回收前后活动对象的总大小。在次要回收之后,大小包括一些垃圾对象(不再存在)但无法回收。这些对象要么包含在 tenured 层代中,要么从 tenured 层代中引用。
括号中的下一个数字(例如,(776768K) 再次从第一行开始)是提交的堆大小:无需从操作系统请求更多内存即可用于 Java 对象的空间量。请注意,此数字仅包括一个幸存者空格。除了在垃圾回收期间,在任何给定时间都只会使用一个幸存者空间来存储对象。
该行的最后一项(例如,0.2300771 秒)表示执行收集所花费的时间,在本例中大约为四分之一秒。
第三行中主要集合的格式类似。
命令行选项 -XX:+PrintGCDetails
会打印有关集合的其他信息。此处显示了使用串行垃圾回收器的 -XX:+PrintGCDetails 的输出示例。
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]
这表明次要集合恢复了大约 98% 的年轻一代 DefNew:64575K->959K(64576K),耗时 0.0457646 秒(约 45 毫秒)。
整个堆的使用率减少到大约 51% (196016K->133633K(261184K)),并且收集有一些轻微的额外开销(超过年轻一代的收集),如最终时间 0.0459067 秒所示。
选项 -XX:+PrintGCTimeStamps
在每个集合的开头添加时间戳。这对于查看垃圾回收发生的频率非常有用。
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]
收集在应用程序执行大约 111 秒后开始。次要集合大约在同一时间开始。此外,还显示了由 Tenured 描述的主要集合的信息。使用代的使用量减少到大约 10% (18154K->2311K(24576K)),耗时 0.1290354 秒(约 130 毫秒)。
确定“代”的大小 Sizing the Generations
虚拟机初始化时将保留堆的整个空间。可以使用 -Xmx 选项指定预留空间的大小。
如果 -Xms 参数的值小于 -Xmx 参数的值,则不会立即将保留的所有空间提交到虚拟机。
未提交的空间在此标记为 “virtual”。堆的不同部分(老一代和年轻一代)可以根据需要增长到虚拟空间的极限。
一些参数是堆的一部分与另一部分的比率。参数 NewRatio 表示终身一代与年轻一代的相对大小。
总堆
默认情况下,虚拟机会增大或缩小每个集合的堆,以尝试将每个集合中活动对象的可用空间比例保持在特定范围内。此目标范围由参数-XX:MinHeapFreeRatio=<minimum>和 -XX:MaxHeapFreeRatio=<maximum> 设置为百分比,总大小在下面以-Xms<min>为界,以-Xmx<max>为界。
以默认参数为例,若代中可用空间占比 < 40%,则代会扩展至保持40%可用。若可用空间超过70%,则代会收缩。
应用程序堆大小的一般准则
- 默认大小通常太小,需要为虚拟机授予尽可能多的内存,除非遇到暂停问题。
- 若 -Xms 和 -Xmx 设为相同值,可以提高可预测性,原因是删除了最重要的大小调整决策。但如果做出了错误的选择,虚拟机将无法进行补偿。
- 通常随着处理器数量的增加,内存也会增加,因为分配可以并行化。
新生代(年轻代)
专用于新生代的堆比例是制约 GC 性能的第二影响因素。年轻代越大,minor GC 频率越低。
- 对于有界堆大小,较大的年轻代意味着较小的终身代,这将增加 major GC 的频率。
- 最佳选择取决于应用程序分配的对象的生命周期分布。
默认情况下,新生代大小由参数 NewRatio 控制。
- 设置 -XX:NewRatio=3 表示年轻一代和老一代之间的比率为 1:3。换句话说,eden 和 survivor 空间的总大小将是总堆大小的四分之一。
参数 NewSize 和 MaxNewSize 分别为上限和下限。
- 将这些值设置为相同的值可以修复新生代,就像将 -Xms 和 -Xmx 设置为相同的值可以修复总堆大小一样。
- 这对于以比 NewRatio 允许的整数倍数更精细的粒度调整年轻代非常有用。
幸存者空间
SE8默认值:
SE23默认值:
可以使用参数 SurvivorRatio can used 来调整幸存者空间的大小,但这通常对性能并不重要。
- 例如,-XX:SurvivorRatio=6 将 eden 和幸存者空间之间的比率设置为 1:6。换句话说,每个幸存者空间的大小将是 eden 的六分之一,因此是年轻一代大小的八分之一(不是七分之一,因为有两个幸存者空间)。
如果幸存者空间太小,则复制集合将直接溢出到永久代中。如果幸存者空间太大,它们将毫无用处。
- 在每次垃圾回收时,虚拟机都会选择一个阈值数字,即对象在永久保留之前可以复制的次数。
- 选择此阈值是为了使幸存者保持半满。命令行选项
-XX:+PrintTenuringDistribution(并非在所有垃圾回收器上都可用) 可用于显示此阈值和新一代对象的年龄。它还可用于观察应用程序的生命周期分布。
新生代的最大大小将根据总堆的最大大小和 NewRatio 参数的值计算得出。
- MaxNewSize 参数的“不限”默认值意味着计算值不受 MaxNewSize 的限制,除非在命令行上指定了 MaxNewSize 的值。
应用程序的一般准则
- 首先确定可以为虚拟机提供的最大堆大小。然后根据年轻代的规模绘制性能指标,以找到最佳设置。最大堆大小应始终小于计算机上安装的内存量,以避免过多的页面错误和抖动。
如果总堆大小是固定的,则增加新生代大小需要减小终身代大小。保持 Tenured 代足够大,以容纳应用程序在任何给定时间使用的所有实时数据,以及一定量的松弛空间(10% 到 20% 或更多)。
- 根据前面规定的对终身代的约束:
- 给年轻一代足够的内存。
- 随着处理器数量的增加,增加新生代大小,因为分配可以并行化。