【问题标题】:Tuning GC for Java audio application为 Java 音频应用程序调整 GC
【发布时间】:2011-06-13 08:02:18
【问题描述】:

我注意到在 java 中播放音频时,gc 中的 MarkSweepCompact 阶段太长,导致短暂的静音,这是不可接受的。所以我需要使用低暂停 gc。我已经尝试过 Parallel 和 CMS,它们似乎工作得更好,因为我认为暂停时间更短,而且它们不像默认那样频繁地进行完整收集。

到目前为止,我已经使用以下 ParallelGC 选项测试了我的程序:

-XX:+UseParallelGC 
-XX:MaxGCPauseMillis=70

对于 ConcurrentMarkSweep:

-XX:+UseConcMarkSweepGC
-XX:+CMSIncrementalMode
-XX:+CMSIncrementalPacing

我也尝试过 G1GC,但它在 java 6 中仍处于试验阶段。两种模式的选项:

-Xms15m
-Xmx40m
-XX:+UnlockExperimentalVMOptions
-XX:+CMSClassUnloadingEnabled
-XX:+TieredCompilation
-XX:+AggressiveOpts
-XX:+UseAdaptiveSizePolicy
-Dsun.java2d.noddraw=false
-Dswing.aatext=true
-XX:MaxPermSize=25m
-XX:MaxHeapFreeRatio=10
-XX:MinHeapFreeRatio=10

在这种情况下哪个 GC 更好?是否可以优化这些设置以实现最佳 CPU 性能和最小内存使用量?

EDIT 为了识别暂停,我记录了将音频数据写入输出线的时间,通常在 92 到 120 毫秒之间(我正在写入 16384 字节 = ~92 毫秒),在 Full GC 时进行广告运行,它是 200+ 毫秒:

65.424: [Full GC (System) [PSYoungGen: 872K->0K(2432K)] [PSOldGen: 12475K->12905K(16960K)] 13348K->12905K(19392K) [PSPermGen: 15051K->15051K(22272K)], 0.2145081 secs] [Times: user=0.20 sys=0.00, real=0.21 secs] 
Was writing 16384 bytes, time to write 263 ms

EDIT2 我的应用程序的分配模式如下:它在启动时加载一堆对象,然后开始播放,我猜之后的大部分对象都是由 gui 分配的,因为盯着/暂停音频不会改变 GC 图。这是 visualgc 使用并行 gc 显示的内容:

图表在启动时开始,我开始播放。标记为

1) 声音延迟和完整 gc,我认为它增加了旧尺寸:

101.646: [Full GC [PSYoungGen: 64K->0K(6848K)] [PSOldGen: 15792K->12773K(19328K)] 15856K->12773K(26176K) [PSPermGen: 15042K->14898K(23808K)], 0.2411479 secs] [Times: user=0.19 sys=0.00, real=0.24 secs]

2) 我打开应用程序窗口并暂停播放。没有什么真正改变,稍后它会增加伊甸园的大小。

3) 我打开窗口并重新开始播放。

所以我需要增加分配的旧代大小?我怎么做?我正在运行 -XX:NewRatio=10 和 -XX:NewSize=10m

谢谢。

【问题讨论】:

    标签: java performance audio garbage-collection


    【解决方案1】:

    您提供的日志太小,无法提供真正的分析,但它说它花了 200 毫秒做 v little 因为老一代基本上是满的。这意味着你的堆太小或者你有内存泄漏。在这种情况下,您无法调整 GC 算法。因此,本回复的其余部分是关于如何从应用程序中获取更多信息和/或在消除内存泄漏或拥有更大堆后如何调整 GC。

    在很大程度上,低暂停意味着尽你所能将集合保留为年轻集合。

    您确实需要准确记录开始和结束写入的时间,然后将其与在此期间 JVM 中发生的 STW 暂停相关联,否则您真的不知道可能导致问题的原因或问题的严重程度.

    我会立即做的事情;

    1. 更改您的日志记录,以便输出单行,脚本可以轻松解析(可能是开始时间、结束时间、持续时间)
    2. 添加 PrintGCApplicationStoppedTime 和 PrintGCApplicationConcurrentTime 开关,以便您获得每个 STW 暂停的记录,而不仅仅是 GC 事件
    3. 使用最新的 JVM(即6u23),因为在过去一两年中对热点进行了很多改进,因此需要使用较旧的 JVM
    4. 你不会说你是否受到内存限制,但如果可以的话,我肯定会增加堆大小,40M 非常小,所以你没有太多空间可以玩
    5. 在连接 visualgc 的情况下运行应用程序,它可以更全面地了解 IMO 的情况,因为您可以同时查看所有不同的视图

    关键是确定空间不足的地方以及原因。这个问题的答案可能在于你的应用程序的分配模式是什么样的,它是否会生成大量短暂的对象,以至于你真的很快就烧毁了你的小伊甸园?任期阈值是否太高,以至于您在幸存者空间中对对象进行 ping 操作,然后才能获得任期,从而迫使频繁的任期 gcs(慢)?

    还有一些需要记住的事情......

    • iCMS(增量)旨在用于 1 或 2 核机器,这是否描述了您的机器?你有多少个核心?您可能只想放弃该选项
    • CMS 确实有一个单线程阶段(初始化标记),这可能会伤害到您
    • CMS 通常比其他收集器更喜欢更大的堆,你的堆很小

    在 visualgc 图表添加到问题后进行编辑 由于您的内存有限,因此您需要充分利用您拥有的空间,唯一的方法是通过重复的基准测试......理想情况下使用可重复的测试。

    • 您可以使用-Xmn 指定设置年轻代的大小,剩余部分将分配给tenured。
    • 您可能希望调整对幸存者空间的使用,以便在它们被交换之前让它们变得更充分,并让对象在它们获得终身使用之前在那里存活更长时间
      • -XX:TargetSurvivorRatio=90 设置它,因此在复制之前,幸存者空间需要 90% 满,显然在复制成本和使用空间之间需要权衡
      • 使用-XX:+PrintTenuringDistribution来显示每个空间的大小和情况,你也可以在visualgc中看到这个
      • 使用-XX:+MaxTenuringThreshold 来指定一个对象在它被永久保存之前可以在年轻集合中存活多少次(从一个幸存者空间复制到另一个),例如如果你知道你只会得到短暂的垃圾或永远存在的东西,那么将其设置为 1 是明智的
    • 您需要了解触发终身收藏的原因,并可能考虑采取措施使其稍后触发
      • 对于 CMS,这可能涉及调整 -XX:CMSInitiatingOccupancyFraction=<value>,例如设置为 80,它会在 80% 的终身使用率时触发 CMS(注意:出错是一件坏事,所以你可能更喜欢让热点管理它;设置得太小,它收集的频率太高,会杀死性能,设置它太大,它可能触发太晚,导致计划外的完整收集和相应的较长暂停时间
    • 如果确实是旧集合对您造成了伤害,并且您需要低停顿,那么请使用 CMS 和 ParNew

    最后找个分析器,搞清楚垃圾是从哪里来的,你可能会发现控制垃圾产生的速度然后把精力投入到可以进行 GC 调整的黑洞中更容易!

    【讨论】:

    • 是的,我的内存有限,它是一个音频播放器,使用的空间不应超过 30-40 Mb。一般来说,我的堆大约是半满的。
    • visualgc 是一个很棒的工具,谢谢,我已经用 visualgc 图表更新了问题。
    【解决方案2】:

    这意味着太多的对象被提升出伊甸园空间,因为主 GC 不应该处理太多。您可以使用 -XX:NewRatio 增加分配给新一代的空间比例。尝试 10 并向上移动。
    更好的是,研究如何减少程序中的对象仍然被引用的范围。

    【讨论】:

      【解决方案3】:

      好的,简而言之,您对系统有一个非功能性要求,但未指定满足此要求。 “正确”的答案是使用具有实时能力的 JVM 实现。但大多数都很昂贵,我假设您会接受 99.9% 正确的解决方案。

      首先想到,你应该做的就是找到一种方法来衡量这种中断。否则,任何比较不同垃圾收集器的实验都注定是不可靠的。

      在这个介绍性声明之后,让我们来解决你的问题:

      您说垃圾收集器在声音播放中引入了暂停。您的选择是:

      1. 使用更合适的选项改进垃圾收集器。
      2. 产生更少的垃圾。
      3. 定期调用垃圾收集器,但这很可能导致相反的效果。你必须测量!
      4. 使用延迟隐藏技术来减少垃圾收集器引起的暂停的影响。

      总结:如果你真的想摆脱这个问题,(1) 找到一种方法来衡量它,(2) 做实验,(3) 找到根本原因,(4) 解决根本原因, (5) 衡量你是否真的解决了它。

      【讨论】:

      • 我正在使用 jconsole 来监控 gc,我现在正在试验。
      • 你能在visualvm或jconsole中记录声音和图表,以便找出声音输出间隙时发生的情况吗?
      • 我没有记录声音,而是记录了将音频写入输出线所需的时间,请参阅我的编辑。
      【解决方案4】:

      我知道这是一个老问题,而且 OP 可能甚至不再感兴趣,但让我感到困扰的是这些行在他的配置中:

      -XX:MaxHeapFreeRatio=10
      -XX:MinHeapFreeRatio=10 
      

      对我来说,这意味着他的虚拟机将尝试不断地从系统请求内存或释放它——我很确定这两个数字之间一定存在差距。

      另外,对于任何其他尝试构建实时 Java 系统的人来说,诀窍是先分配所有对象,然后再不分配其他任何东西。

      这可能很棘手,但从长远来看也不是不可能的——打开 -verbose:gc 并删除“New”和其他分配内存的东西,直到你根本看不到任何 gcs。

      顺便说一句,在 GUI 中,这意味着预先创建所有的 GUI 元素并且永远不会释放它们,只是隐藏和显示。这也意味着没有字符串操作(仅使用 StringBuffers 和字符串常量——这是最难解决的问题,因为很多系统调用都依赖于字符串)

      【讨论】:

      • 嗨。是的,我确实放弃了那个项目,但感谢您的回答。我设置这两个参数以最小化应用程序的内存占用。我猜将它们设置为相同的值可能会导致每次 gc 上的堆大小发生变化,从而增加 gc 时间。我目前将其设置为 20/10。而且它仍然占用内存:(
      • 而我最终所做的是将长寿对象的数量降至最低,并使年轻和生存空间更大,以便尽可能少的对象进入老一代。它几乎成功了。
      • 是的,java 似乎很喜欢记忆。我遇到这个是为了寻找一种最小化占用空间而不是暂停的方法...我有一个 10gb 的 tomcat,这让我的开发机器非常不开心。我注意到的一件事是,当我将其设置为 10/20 时,它并没有像我将其设置为 20/40 时那样有帮助,我认为有一些下限,之后它开始忽略数字(默默地,当然)。
      • 有趣,也许有一些最小值,但在源代码中找到它几乎是不可能的。我想知道,您的开发机器如何处理 10gb 的 gc'ing?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-19
      • 2016-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-29
      相关资源
      最近更新 更多