介绍
我决定结合我的两个软件爱好,并在PCGen上进行一些分析, PCGen是一种流行的基于Java的开放源代码角色生成器,用于角色扮演游戏。 我用Censum ,我们( jClarity的)新的垃圾收集日志分析工具来进行分析。
本文假定您对JVM上的垃圾回收(GC)有所了解。 如果您不熟悉GC,那么建议您加入我们的jClarity朋友计划 。 我们正在建立有关GC的知识库,以便与整个Java社区自由共享,我们非常希望您能对此进行验证!
项目
我正在使用的两个项目是PCGen(我正在对其进行分析的项目)和Censum(GC分析工具)。
PCGen
PCGen是针对d20角色扮演系统(例如《星球大战》和《龙与地下城》)的流行角色生成器和维护程序。 这是一个长期运行的项目(> 10年),由一个大型(约750,000 LOC)Java Swing桌面工具组成,该工具具有大量专有格式的数据文件。 免责声明:我是PCGen的主席。
PCGen是一个数据密集型工具。 为了驱动其规则引擎并满足响应式UI的要求(具有大量详细信息),许多此类数据已预先加载并保存在内存中。 用户过去曾报告以下问题。
- 在处理多个字符和/或高级字符时,经常会出现暂停。
- 创建高级别角色或创建的角色超过2个时,PCGen就会死亡。 更多精通技术的用户报告说,他们在pcgen日志文件中看到OutOfMemoryError。
为了减轻PCGen(6.0)最新版本中的这种不良性能,已经做了一些工作,因此我决定使用Censum来确定这些更改是否改善了性能。
森森
Censum是jClarity的新垃圾收集分析工具。 重点是使用功能强大的分析来处理原始日志数据,并为繁忙的开发人员(例如PCGen!的贡献者)快速提供简单的英语答案。 免责声明:我是jClarity的CTO。
Censum是一项对开放源代码项目免费的新产品,当然,如果您要购买副本,今天就可以获得免费的评估许可证 (单击Try Censum )!
TLDR –结论
我们有好消息,一些信息和坏消息。
好消息
积极的消息是,PCGen开头的默认堆设置(-Xms256m -Xmx512m)现在足够大小,足以保持PCGen的运行。 即使创建了第5个复杂字符,也没有OutOfMemoryError。
Censum显示,一旦运行了完整的GC(通常在创建了每个新字符之后),便会恢复很大一部分堆,并且每个字符占用大约25-50 MB的堆空间。 我们可以粗略地推断出,起始点(已加载数据)约为125MB,PCGen可以随时轻松地保持大约10-15个字符打开而不会崩溃。 对于一个总经理来说,这可能还不足以让他的哥布林部落开始运转,但是对于大多数常规聚会而言,这当然足够了!
坏消息
负面消息稍多的是Censum报告,PCGen的暂停时间相对较高,很可能是由于过早晋升而引起的。 提早升级过多会导致我们想要的存储速度更快地进入旧一代。 这可能会产生连锁反应,导致收集更多旧的基因组以及完整的GC,自然导致更长的停顿时间。 有关高暂停时间,过早升级以及PCGen可以做什么的更多详细信息,请参见完整的分析部分。
从这里到哪里?
PCGen可以遵循Censum的“权宜之计”建议,以改变年轻一代空间的大小。 通过使用-XX:NewSize标志并将其设置为〜256M,可以缓解高暂停时间的问题。
但是,PCGen的长期解决方案是继续减少其数据结构(特别是玩家角色的表示)的影响。 实际上,这是PCGen今天正在进行的项目!
技术设置
PCGen通常从具有默认堆设置-Xms256m和-Xmx512m的Shell脚本运行。 对该脚本进行了更改,以提供产生可分析的GC日志所需的最少标志集。 添加到java命令的标志是:
-verbose:gc -Xloggc:pcgen_gc.log -XX:+ PrintGCDetails -XX:+ PrintTenuringDistribution
- -verbose:gc和-Xloggc:pcgen_gc.log生成基本日志,该日志输出到名为pcgen_gc.log的文件。
- -XX:+ PrintGCDetails提供Censum执行分析所需的GC分配信息的绝对最小集。
- 最后,-XX:+ PrintTenuringDistribution提供有关对象何时从年轻的世代空间(伊甸园,幸存者1和幸存者2)移动到旧世代空间(使用期限)的有用信息。
所有这些选项对正在运行的JVM几乎没有影响。 您应该始终在生产中启用这些功能!
PCGen在运行Mac OS 10.7.5的MBP上与Oracle Java 7u10一起运行,具有8GB RAM,256MB SSD驱动器和超线程双核2.8Ghz i7处理器。
PCGen活动
PCGen首先加载基本游戏模式和系统文件+基本UI来加载数据源。 下一步是让用户选择要加载的数据源(角色扮演游戏规则集)。 装有“怪兽”套装的流行SRD 3.5 (地牢和巨龙3.5e)已加载。
一个角色( Karianna )被逐级创建到第20个巫师中,该巫师具有满载的咒语,设备和熟悉的猫(在PCGen中实际上是第2个角色)。 此后添加了几个更复杂的角色,包括大龙蓝龙(大量数据!)。
分析
我将介绍最初的数据加载用例,然后介绍常规用法(创建字符)。
资料载入
我很好奇初始数据加载对内存的影响。 尽管使用具有PCGen的SSD驱动器可以缩短加载时间,但毫无疑问,这是项目目标! 这是Censum在GC之后在堆分配方面显示的内容。
如您所见,数据的初始加载在数据加载结束时导致了许多年轻的世代集合和一个旧的(永久的)GC事件。 堆使用率最高可攀升至约325MB,但在收集垃圾后,堆使用率又降至约100MB。 加载大约15本厚规则书的数据还不错!
但是,PCGen的数据加载有点像Web /应用程序服务器(例如Tomcat)的启动期。 就您的GC分析而言,通常最好将其排除在一次性启动之外,而不是分析正在运行的应用程序。
创建角色
创建Karianna并将她提升到20级需要填写13个主选项卡的详细信息〜20个子选项卡和大量数据! 还创建了另外4个字符,它们具有相似的复杂性,其中一些友好(猫很熟悉),而有些则不友好(大龙蓝龙)。
该过程的一些屏幕截图如下:
技能专长
设备
嵌入式字符表
Censum的分析
打开日志文件后,Censum立刻将我带到了“分析摘要”屏幕,该屏幕使我一目了然地了解了PCGen的垃圾回收的进行情况。
好消息
我立即知道:
- 我有正确的标志来收集足够的GC数据以进行分析以使其正常工作
- 我的GC吞吐量很好(PCGen花费时间在运行,而不是在进行垃圾收集)
- 没有讨厌
System.gc()调用(通常不是很好的做法)。
信息新闻
内存利用率 (内存泄漏是其中的一个子集)和内存池大小是信息性的,因为日志尚未收集24小时的数据(建议的最小值是查看应用程序一个完整工作日的周期)。
坏消息
PCGen似乎具有高的暂停时间以及过早的升级问题。 让我们进一步深入探讨!
高暂停时间
高暂停时间是由于GC收集器必须暂停应用程序线程才能以安全的方式清理对象引用所致。 收集器必须扫描和清理的对象引用越多,暂停时间就越长! 最长的停顿通常是由完整的GC集合引起的,其中清理了整个Java堆(例如,年轻一代和老一代的空间都变得很满)。
作为一个用户,我注意到停顿了两次,还不足以真正打扰我,但我知道我的硬件非常好,而且这些停顿时间对于其他用户而言可能会更糟。
正如Censum指出的那样,暂停所花费的时间的0.15%并不是主要的问题,而是所关心的0.666秒的暂停时间。 但是,我记得最长的暂停时间可能来自PCGen中的初始数据加载。 为了与此相关,Censum提供了一张暂停时间图。
数据加载是最严重的违法行为,但是肯定的是,由于GC已满,因此对于每个创建的角色,每个角色创建点周围都有½秒的良好暂停。 在使用PCGen的情况下,再说一秒钟对我来说并不令人讨厌,但是正如Censum所示,完整的GC需要时间,因此PCGen应该减少完整的GC的数量。
在这种情况下,我们知道由于Censum给我们的另一个警告–提早升级,我们可能会得到比我们想要的更多的完整GC。
提早促销
提早提倡本质上是指应该在年轻一代空间中收集的对象在年龄增长之前就被提升到老一代空间中。 这个“年龄”被称为任职期限,它基于1980年代进行的软件研究和JVM的运行时启发式算法的结合。 由于以下原因,可能会提早升职:
- 创造新物体的速度淹没了年轻一代的空间
- 正在创建的对象太大,无法容纳年轻的一代空间。 例如大的连续内存块。
这具有对旧的发电空间施加压力的效果。 它的填充速度更快,因此会收集更多的旧基因组并最终出现完整的GC,从而导致更频繁的暂停时间。
当我去看看应该提升对象多长时间以及它们被提升多早时,我立即得到了答案。 “任期摘要”屏幕向我们显示了“任期阈值”设置为15(对象可以在年轻一代中存活约15个集合,然后自然提升为老一代)。 另请注意,过早地提升了100%的对象!
然后,我去看看何时提升对象。 Censum告诉我,大多数人几乎是在年轻一代就开始晋升的(Tenuring Threshold为1)。 该值向我表明,到达的对象对于当前的年轻一代而言太大。 但是,我仔细检查并确保对象分配不是很高,Censum告诉我,对象分配的峰值约为2.5GB / s,这仅是大约? 我的MBP的功能,所以我们可以了。
PCGen采取的步骤
PCGen团队的下一步将是查看jmap中的直方图之类的内容,以查看正在创建哪些大对象。 快速检查jmap输出意味着我知道我们有String(字符串),char和byte的大型(多MB)连续数组。
此时PCGen的一个选项是调整年轻一代空间的大小,以便使用
-XX:NewRatio或-XX:NewSize标志。 设置这两个值之一的大小将取决于从jmap用法中发现的结果。 在这种情况下,需要将-XX:NewSize设置为〜256M,以减少暂停次数。
但是,通常这不是一个很好的长期解决方案。 PCGen项目最好减少这些大的连续内存分配的使用。 由汤姆·帕克(Tom Parker)和詹姆斯·登普西(James Dempsey)领导,这正是PCGen团队正在做的事情,将庞大的内存结构从整体式的PlayerCharacter类中提取到了更灵活的较小方面。
摘要
令人惊奇的是,PCGen志愿者为6.0版本付出的辛勤工作为最终用户带来了实实在在的红利!
几年前,我追踪了PCGen用户在加载第25级Kobold Shaman时报告的30秒以上的暂停。 我花了几天的时间才发现这是一个GC /内存问题,因为我只有原始的GC日志文件(它们是多线程的,不确定的野兽)。 展望未来,我肯定会使用像Censum这样的工具来更快地解决这些问题,这意味着我可以继续为PCGen添加新功能!
如果您想解决棘手的GC问题,请不要忘记查看PCGen来满足d20角色扮演游戏的需求和Censum !
参考:来自我们的JCG合作伙伴 Martijn Verburg 的PCGen垃圾收集分析,网址为Are We There Yet博客。
翻译自: https://www.javacodegeeks.com/2013/01/garbage-collection-analysis-of-pcgen.html