直接分配给老年代的巨大对象的大小

Posted

技术标签:

【中文标题】直接分配给老年代的巨大对象的大小【英文标题】:Size of Huge Objects directly allocated to Old Generation 【发布时间】:2014-08-28 09:36:30 【问题描述】:

最近我一直在阅读有关 Java 中不同代的对象分配的内容。大多数情况下,新对象在 Eden(年轻代的一部分)中分配,然后如果满足以下任何条件,它们就会被提升到老年代。

(1) 对象的年龄达到了任期阈值 (2) 从 Eden(或)另一个幸存者空间(从)复制对象时,幸存者空间(to)已满

但也有一种特殊情况,对象直接在老年代分配,而不是从年轻代提升。当我们试图创建的对象很大(可能是几 MB 的数量级)时,就会发生这种情况。


有没有办法知道巨大/巨大物体的大小/限制?我知道 G1 垃圾收集器的巨大对象标准。我只想知道 Java 6 之前或之后的大小限制

感谢您的宝贵时间 :)

【问题讨论】:

请记住,GC 代码也可能对对象类型敏感。众所周知,有些课程通常寿命很长,因此更容易直接获得终身职位。 @HotLicks.. 你能更具体地了解这些长期存在的课程吗.. 一个小例子就可以了 :) @Arkantos - 我在 JVM 工作已经 5 年多了,而且我与 GC 人员讨论水冷却器的时间也更长了,所以目前什么都没有想到。我认为一些内部 JVM 类属于这一类——与进程和文件相关的东西——但我没有具体的例子,也不知道这些年来这些东西是如何演变的。我没有听到讨论过的一件有意义的事情是分析类并识别那些易于在给定应用程序中长期存在的类。 但是一般的一点是,人们不应该假设任何关于任期的决定都是基于单一标准的非黑即白的。 JVM 可能有几个标准,并且算法可能会随 GC 开发人员的心血来潮而改变。 另一点(有点不太一般)是直接将大对象分配到永久空间的原因是伊甸园空间(按设计)相对有限,并且在那里分配大对象会导致GC 频率过高。 【参考方案1】:

您可以使用以下标志设置限制

XX:PretenureSizeThreshold=size

它的默认值为0 我假设默认情况下,如果你不设置它,它不会被考虑为 value=0,这意味着默认情况下没有最大值作为阈值,通过默认对象仅根据 GC 存活次数得到提升

热点版本

java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

要获取所有 vm 选项(支持),您可以运行

java -XX:+PrintVMOptions -XX:+AggressiveOpts -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal  -version

如果未列出,您可以参考hotspot vm option document 或谷歌特定选项


byte[] array = new byte[300*1024*1024];

for(MemoryPoolMXBean memoryPoolMXBean: ManagementFactory.getMemoryPoolMXBeans())
    System.out.println(memoryPoolMXBean.getName());
    System.out.println(memoryPoolMXBean.getUsage().getUsed());

输出:

$ java -Xmx1500m -Xms1500m -Xmn500m -XX:PretenureSizeThreshold=100000000 -XX:+PrintGCDetails JVMMemoryInspection
Code Cache
393664
PS Eden Space
330301752
PS Survivor Space
0
PS Old Gen
0
PS Perm Gen
2749520

【讨论】:

真棒 :) 这就是我要找的。正如你提到的 0 是默认值。我在笔记本电脑上检查了 -XX:+PrintFlagsFinal 中的值及其 0,我什至尝试使用 jinfo 运行示例程序,但它仍然为 0。让我在实际的应用程序服务器上尝试一下,然后回复您。可能在一个长时间运行的应用程序中,这个值可能已经被 JVM 使用人体工程学改变了。 是的可能会被服务器类机器上的人体工程学改变 此外,如果您可以指向任何资源/链接以获取有关这些 JVM 标志实际含义/作用的一些文档。这将是真正的节省时间:) 再次感谢.. 除非在命令行中明确指定,否则不会使用 PretenureSizeThreshold。但即使设置了,也只会在慢速分配路径中检查。 JIT 编译的代码首先尝试在 TLAB 或 Eden 中分配,然后才进入慢速路径。 @JigarJoshi 测试就像byte[] array = new byte[300*1024*1024]; 一样简单 用-Xmx1500m -Xms1500m -Xmn500m -XX:PretenureSizeThreshold=200000000 -XX:+PrintGCDetails 运行java 看看会发生什么。基本上,PretenureSizeThreshold 只是被忽略并且对象在 Eden 中分配。如果将-Xmn设置为400m,则数组将不适合Eden,将直接在Old Gen中分配。【参考方案2】:

HotSpot JVM 可以在年轻代中分配的对象的最大大小几乎与 Eden 的大小一样大(YoungGen 减去两个 Survivor 空间)。

这就是分配粗略的样子:

    使用线程本地分配缓冲区 (TLAB),如果 tlab_top + size tlab_end 这是最快的路径。分配只是tlab_top 指针增量。 如果 TLAB 快满了,请在 Eden 中创建一个新的 TLAB,然后在新的 TLAB 中重试。 如果 TLAB 剩余空间不够,但仍然太大而无法丢弃,请尝试直接在 Eden 中分配对象。 Eden 中的分配也是使用原子操作的指针增量 (eden_top + size eden_end),因为 Eden 在所有线程之间共享。 如果 Eden 中的分配失败,通常会发生次要收集。 如果即使在 Young GC 之后 Eden 中也没有足够的空间,则会尝试直接在老年代进行分配。

【讨论】:

@JigarJoshi 我想是很久以前的剩菜了。在我们的时代,对每个对象分配执行额外的参数检查将是一种矫枉过正。 听起来像是文档中的一个错误,感谢您的精彩解释 感谢您提供如此简单而优雅的答案。 +1 保持简单:) @apangin.. 我在这里添加了支持您的类比的答案.. 让我知道是否需要添加任何内容.. @Arkantos 没关系。谢谢。【参考方案3】:

JVM 标志:

-Xms1G -Xmx1G -Xmn500m -XX:PretenureSizeThreshold=100000000 -XX:+PrintGCDetails

通过将年轻代大小固定为 500MB,eden 大约为 384MB,因此任何大于 384MB 的对象都直接进入 OldGen,而小于 384MB 的对象则分配在 Eden 本身中。您可以在下面找到生成用法

字节[] 数组 = 新字节[400*1024*1024];

PSYoungGen      total 448000K, used 30720K  
    eden space 384000K, 8% used  
    from space 64000K, 0% used  
    to   space 64000K, 0% used      
 ParOldGen       total 536576K, used 409600K  
   object space 536576K, 76% used 

字节[] 数组 = 新字节[300*1024*1024];

 PSYoungGen      total 448000K, used 337920K  
  eden space 384000K, 88% used  
  from space 64000K, 0% used  
  to   space 64000K, 0% used  
 ParOldGen       total 536576K, used 0K 
  object space 536576K, **0% used** 

对于 400MB 分配,伊甸园的使用率为 8%,而老一代的使用率为 76% 对于 300MB 分配,伊甸园使用率为 88%,而老一代使用率为 0% 所以很明显所有大于eden的对象都会直接分配到old gen中。

感谢 apangin 和 Jigar 提供宝贵的见解 :) 我认为 -XX:PretenureSizeThreshold 根本没有考虑。

【讨论】:

以上是关于直接分配给老年代的巨大对象的大小的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java虚拟机——内存分配

如何设置JVM内存分配比例

新生代对象进入老年代的时机:

新生代对象进入老年代的时机:

JVM系列三:内存分配与回收策略

JVM老年代和新生代的比例