Java非常大的堆大小

有没有人有在Java中使用非常大的堆,12 GB或更高的经验?

  • GC是否使程序无法使用?
  • 你使用什么GC参数?
  • 哪个JVM,Sun或BEA会更适合这个?
  • 哪个平台,Linux或Windows,在这样的条件下performance更好?
  • 在Windows的情况下,在如此高的内存负载下,64位Vista和XP之间会有什么性能差距?

如果您的应用程序不是交互式的,并且GC暂停对您来说不是问题,那么对于64位Java来说,处理非常大的堆(即使是数百GB)应该没有任何问题。 我们也没有注意到Windows或Linux的任何稳定性问题。

但是,当你需要保持GC暂停时,事情变得非常恶劣:

  1. 忘记默认吞吐量,停止世界GC。 它会暂停你申请几十秒钟(<〜30 GB),几分钟(>〜30 GB)。 而购买更快的DIMM将无济于事。

  2. 最好的select可能是CMS收集器,由-XX:+ UseConcMarkSweepGC启用。 CMS垃圾收集器仅在初始标记阶段和重新标记阶段停止应用程序。 对于像<4 GB这样的非常小的堆来说,这通常不是问题,但是对于创build大量垃圾和大堆的应用程序来说,标记阶段可能需要相当长的时间 – 通常远远less于完全停止世界,但仍然可能是一个非常大的堆的问题。

  3. 当CMS垃圾收集器没有足够的速度完成操作之前,终身代填满,它回落到标准停止世界GC。 对于16 GB大小的堆,期望〜30秒或更长时间的暂停。 您可以尽量避免这种情况,尽可能降低您应用程序的长期垃圾生产率。 请注意,运行您的应用程序的内核数量越多,得到这个问题就越大,因为CMS只使用一个内核。 很明显, 不能保证CMS不会退回到STW收集器。 而当它发生时,通常发生在高峰负载,你的应用程序已经死了几秒钟。 您可能不想签署这样一个configuration的SLA。

  4. 那么,有这个新的G1的东西。 这是理论上devise的,以避免与CMS的问题,但我们已经尝试过,并观察到:

    • 其吞吐量比CMS差。
    • 从理论上讲,要避免首先收集stream行的内存块,然而它很快会达到几乎所有块都“stream行”的状态,而且它所基于的假设简单地停止工作。
    • 最后,G1的停止回退仍然存在; 问甲骨文,当代码应该运行。 如果他们说“从来没有”,问他们为什么代码在那里。 所以恕我直言G1真的不会让Java的巨大堆问题消失,它只会使它(可以说)更小一点。
  5. 如果你拥有大内存的大型服务器,那么你可能还会花费大量的商业硬件加速的,像ASPUL提供的GC技术。 我们有他们的服务器之一,384 GB的RAM,它真的很好 – 在GC中没有停顿,0行停止世界的代码。

  6. 编写你的应用程序的那个需要大量内存的应用程序,比如LinkedIn用社交图处理。 你仍然不会通过这样做来避免所有的问题(例如堆碎片),但是保持较低的停顿肯定更容易。

我是阿祖尔系统的首席执行官,所以我显然在这个问题上有我的看法! :) 话虽如此…

Azul的首席技术官Gil Tene对垃圾收集相关的问题有一个很好的概述,他在“ 了解Java垃圾收集和你能做些什么”的演示文稿中回顾了各种解决scheme,本文还有其他细节: http:// http://www.infoq.com/articles/azul_gc_in_detail

我们的Zing JVM中的Azul C4垃圾收集器既是并行的,又是并行的,并且对于新老两代都使用相同的GC机制,在这两种情况下同时工作和压缩。 最重要的是,C4没有停止世界的倒退。 所有压缩与正在运行的应用程序同时执行。 我们有客户运行非常大(数百GB),更糟糕的情况下,GC暂停时间<10毫秒,根据应用程序经常less于1-2毫秒。

CMS和G1的问题是,在某些时候Java堆内存必须被压缩,并且这两个垃圾收集器都停止了这个世界/ STW(即暂停应用程序)来执行压缩。 所以,当CMS和G1可以推出STW暂停时,他们不会消除它们。 然而,Azul的C4,完全消除了STW暂停,这就是为什么Zing即使是巨大的堆大小也有如此低的GC暂停。

我们有一个应用程序,我们分配12-16Gb,但在正常运行期间,它确实只能达到8-10。 我们使用Sun JVM(尝试过IBM,这有点儿是一场灾难,但是我们可能对此毫无知觉……我有一些发誓的朋友 – 在IBM工作)。 只要你给你的应用程序呼吸的空间,JVM可以处理大堆不太多的GC。 大量的“额外”记忆是关键。
Linux几乎总是比Windows更稳定,当它不稳定的时候,弄清楚为什么会更容易。 Solaris也是坚如磐石的,你也得到了DTrace :)有了这样的负载,为什么你会使用Vista或XP? 你只是在问问题。 我们对GC的参数不做任何事情。 我们确定最小分配等于最大值,所以它不是经常尝试resize,但就是这样。

在Linux和Solaris下,我已经在两个不同的应用程序上使用了超过60 GB的堆大小,分别使用Sun 1.6 JVM的64位版本(显然)。

我从来没有遇到基于Linux的应用程序的垃圾收集问题,除非在堆大小限制附近提升。 为了避免该场景固有的抖动问题(花费太多时间进行垃圾收集),我简单地优化了整个程序中的内存使用情况,以便在64 GB的堆大小限制以下,峰值使用率大约为5-10%。

然而,在Solaris下运行的另一个应用程序,我遇到了很大的垃圾收集问题,因此需要进行大量的调整。 这主要包括三个步骤:

  1. 通过-XX:+ UseParallelGC -XX:+ UseParallelOldGC JVM选项启用/强制使用并行垃圾回收器,以及通过-XX:ParallelGCThreads选项控制使用的GC线程数。 有关更多详细信息,请参阅“ Java SE 6 HotSpot虚拟机垃圾收集调优 ”。

  2. 广泛的,似乎荒谬的局部variables设置为“无效”后,不再需要。 其中大部分是在超出范围之后应该已经有资格进行垃圾回收的variables,并且由于不复制引用,所以它们不是内存泄漏情况。 然而,这种“手持”策略有助于垃圾回收,这在某些Solaris平台下的应用程序中出于某种原因是非常必要的。

  3. 在大量的临时对象分配之后,在关键代码段中select性地使用System.gc()方法调用。 我知道使用这些调用的标准警告,以及通常应该是不必要的说法,但是我发现它们在运行这种内存密集型应用程序时,在处理垃圾回收时非常重要。

通过以上三个步骤,可以将这个应用程序保留下来,并且可以高效地运行大约60 GB的堆使用量,而不是将其控制到128 GB的堆大小限制。 特别是并行垃圾回收器是非常有用的,因为当有很多对象时,主要的垃圾收集周期是昂贵的,也就是说,主要垃圾回收所需的时间是堆中对象数量的函数。

我不能评论这个规模的其他特定于平台的问题,也不能使用非Sun(Oracle)JVM。

12Gb应该是一个体面的JVM实现,如Sun的热点没有问题。 我build议你在使用SUN VM时使用Concurrent Mark和Sweep colllector(-XX:+ UseConcMarkSweepGC)。如果在GC期间所有的线程都停止,那么你可能会面临很长的“停止世界”阶段。

操作系统不应该对GC性能产生很大的影响。

你当然需要一个64位的操作系统和一台具有足够物理内存的机器。

我build议还考虑采取一个堆转储,并查看您的应用程序中的内存使用情况可以提高,并分析转储的东西,如Eclipse的MAT 。 在MAT页面上有几篇关于寻找内存泄漏的文章。 您可以使用jmap来获取转储,例如…

jmap -heap:format=b pid 

XP可以解决的最大内存是4 gig( here )。 所以你可能不想使用XP(使用64位操作系统)。

如果您切换到64位,您将使用更多的内存。 指针变成8个字节,而不是4个。如果你正在创build大量的对象,可以注意到,因为每个对象都是一个引用(指针)。

我最近使用Sun 1.6 JVM分配了15GB的Java内存,没有任何问题。 虽然全部只分配一次。 在初始金额之后没有多less内存被分配或释放。 这是在Linux上,但我想像Sun JVM在64位Windows上也能工作。

从Java 6上的文章可以帮助你: http : //java.sun.com/developer/technicalArticles/javase/troubleshoot/

你应该尝试运行visualgc对你的应用程序。 这是一个堆可视化工具,它是http://java.sun.com/performance/jvmstat/上的jvmstat下载的一部分。;

这比读取GC日志容易得多。

它可以快速帮助您了解堆的部分(几代)如何工作。 虽然你的总堆可能是10GB,堆的各个部分将会小得多。 在堆的伊甸园部分的GC相对便宜,而老一代的全部GC是昂贵的。 为了使伊甸园规模庞大,老一辈人几乎不曾碰过这样的大堆,这是一个很好的策略。 这可能会导致一个非常大的整体堆,但是如果JVM从不触及页面,它只是一个虚拟页面,不需要占用RAM。

几年前,我比较了JRockit和Sun JVM的12G堆。 JRockit赢了,Linux巨大的支持使我们的testing运行速度提高了20%。 YMMV作为我们的testing是非常处理器/内存密集型,主要是单线程。

这里有一篇关于gc的文章,来自Java冠军之一 – http://kirk.blog-city.com/is_your_concurrent_collector_failing_you.htm

柯克,作者写道:“给我你的GC日志

我目前正在研究Sun JVM生成的GC日志。 由于这些日志不包含与商业相关的信息,因此应该减轻对保护信息的关注。 我只需要提及操作系统的日志,JRE的完整版本信息以及您设置的所有与堆/ gc相关的命令行开关。 我还想知道,如果您正在运行Grails / Groovey,JRuby,Scala或者Java以外的其他软件。 最好的设置是-Xloggc :. 请注意,该日志在达到操作系统大小限制时不会翻转。 如果我发现任何有趣的事情,我会很高兴给你一个非常快速的概要作为回报。 “

如上所述,如果您有一个非交互式程序,则默认(压缩)垃圾回收器(GC)应该可以正常运行。 如果您有一个交互式程序,并且您(1)不能比GC更快地分配内存,并且(2)不要创build太大的临时对象(或对象集合)最大的JVM内存)为GC解决,然后CMS是给你的。

如果您有一个GC没有足够呼吸空间的交互式程序,就会遇到麻烦。 无论你拥有多less内存,情况都是如此,但你拥有的内存越多,获得的内存越多。 这是因为当内存过低时,CMS将耗尽内存,而压缩GC(包括G1)会暂停一切,直到所有内存都被检查为垃圾。 这个停止世界的停顿越大,你拥有的记忆就越多。 相信我,你不希望你的servlet停一会儿。 我在G1中写了一个关于这些暂停的详细的StackOverflow答案。

从那以后,我的公司已经转向了Azul Zing。 它仍然不能处理你的应用程序真的需要比你有更多的内存的情况,但直到那一刻它像梦一样运行。

但是,当然,Zing不是免费的,它的特殊酱汁是专利的。 如果你有更多的时间比金钱,尝试重写你的应用程序使用JVM群集。

即将到来的,甲骨文公司正在开发一种用于数千兆字节堆的高性能GC。 但是,截至今天,这不是一个选项。

尽pipeitanium并不是一个受欢迎的目的地,但sun已经有一段Itanium64位的jvm了。 solaris和linux 64位JVM应该是你应该做的。
一些问题

1)你的应用程序是否稳定?
2)您是否已经在32位JVM中testing了应用程序?
3)可以在同一个盒子上运行多个JVM吗?

我期望Windows的64位操作系统在大约一年左右的时间内稳定下来,但在此之前,solaris / linux可能会更好。