如何在应用程序中保持5GB持续内存,而不会因GC导致性能不佳?

时间:2014-06-06 08:43:37

标签: java performance garbage-collection

我的应用是地理位置应用。 由于要求响应时间短,我的每个实例都会将所有点加载到内存中并将它们存储在结构中(四叉树)。

我们每分钟加载所有点(与db同步)并将它们放在几个四叉树中。

我们现在有0.5GB积分。 我正在努力准备下一级5GB积分。 JVM: -XX:NewSize = 6g -Xms20g -Xmx20g -XX:+ UseConcMarkSweepGC -verboseGC -XX:+ PrintGCTimeStamps -XX:+ PrintGCDateStamps -XX:+ PrintGCDetails

实例的启动需要很多时间,因为GC会额外受到GC的影响。

我想对大堆的GC有一些参考。

我可以想到几个解决方案:

  1. 仅刷新数据库中的更改,而不是每次都加载所有数据库。 缺点 - 在应用程序的早期阶段仍然会遇到GC,长时间GC。

  2. 关闭堆解决方案。在四元树中存储点ids并将点存储在堆外。 缺点 - 序列化时间,地理结构是一个很少的对象而不是简单对象的复合体。

  3. 对于每个实例,使用针对此实例的结构和查询创建其他实例。 geo实例将保存长寿命对象,并且可以对长寿命对象进行GC调整。 缺点 - 复杂性和响应时间。

  4. 对于持有少量GIG长寿命对象的应用程序的文章的任何引用都将非常受欢迎。

    在Ubuntu(亚马逊)上运行。 Java 7。 Dosnt有内存限制。

    每次刷新数据时问题都是暂停时间很长。 Gc

    刷新的Gc日志:

    2014-06-15T16:32:58.551+0000: 1037.469: [GC2014-06-15T16:32:58.551+0000: 1037.469: [ParNew: 5325855K->259203K(5662336K), 0.0549830 secs] 16711893K->11645244K(20342400K), 0.0551490 secs] [Times: user=0.71 sys=0.00, real=0.05 secs] 
    
    2014-06-15T16:33:02.383+0000: 1041.302: [GC2014-06-15T16:33:02.383+0000: 1041.302: [ParNew: 5292419K->470768K(5662336K), 0.0851740 secs] 16678460K->11856811K(20342400K), 0.0853260 secs] [Times: user=1.09 sys=0.00, real=0.09 secs] 
    
    2014-06-15T16:33:06.114+0000: 1045.033: [GC2014-06-15T16:33:06.114+0000: 1045.033: [ParNew: 5503984K->629120K(5662336K), 1.5475170 secs] 16890027K->12193877K(20342400K), 1.5476760 secs] [Times: user=5.49 sys=0.61, real=1.55 secs] 
    
    2014-06-15T16:33:11.145+0000: 1050.063: [GC2014-06-15T16:33:11.145+0000: 1050.063: [ParNew: 5662336K->558612K(5662336K), 0.7742870 secs] 17227093K->12758866K(20342400K), 0.7744610 secs] [Times: user=3.88 sys=0.82, real=0.77 secs] 
    
    2014-06-15T16:33:11.920+0000: 1050.838: [GC [1 CMS-initial-mark: 12200254K(14680064K)] 12761216K(20342400K), 0.1407080 secs] [Times: user=0.13 sys=0.01, real=0.14 secs] 
    
    2014-06-15T16:33:12.061+0000: 1050.979: [CMS-concurrent-mark-start]
    
    2014-06-15T16:33:14.208+0000: 1053.127: [CMS-concurrent-mark: 2.148/2.148 secs] [Times: user=19.46 sys=0.44, real=2.15 secs] 
    
    2014-06-15T16:33:14.208+0000: 1053.127: [CMS-concurrent-preclean-start]
    
    2014-06-15T16:33:14.232+0000: 1053.150: [CMS-concurrent-preclean: 0.023/0.023 secs] [Times: user=0.14 sys=0.01, real=0.02 secs] 
    
    2014-06-15T16:33:14.232+0000: 1053.150: [CMS-concurrent-abortable-preclean-start]
    
    2014-06-15T16:33:15.629+0000: 1054.548: [GC2014-06-15T16:33:15.630+0000: 1054.548: [ParNew: 5591828K->563654K(5662336K), 0.1279360 secs] 17792082K->12763908K(20342400K), 0.1280840 secs] [Times: user=1.65 sys=0.00, real=0.13 secs] 
    
    2014-06-15T16:33:19.143+0000: 1058.062: [GC2014-06-15T16:33:19.143+0000: 1058.062: [ParNew: 5596870K->596692K(5662336K), 0.3445070 secs] 17797124K->13077191K(20342400K), 0.3446730 secs] [Times: user=3.06 sys=0.34, real=0.35 secs] 
    
     CMS: abort preclean due to time 2014-06-15T16:33:19.832+0000: 1058.750: [CMS-concurrent-abortable-preclean: 5.124/5.600 secs] [Times: user=35.91 sys=1.67, real=5.60 secs] 
    

3 个答案:

答案 0 :(得分:2)

人们对设计要点做了一些非常好的评论,你应该考虑一下。如果你考虑GC影响,你的问题有点难以回答,因为垃圾收集调整不是一门精确的科学。您可能希望考虑的是您正在使用的实际GC算法。但首先是一点背景:

垃圾收集是分代的,它的性能依赖于high infant mortality对象的速率 - 即在应用程序流程中创建并快速销毁对象。您持有大型物体(由于缓存性质),这意味着您将填满伊甸园,并且GC将被要求将这些物品推广到幸存者和终身空间。

Hotspot Heap Structure

这意味着如果您需要回收此内存,则可能需要暂停,因此需要更长的GC暂停时间来收集。但是,考虑到堆的大小,这可能不是问题,因为它可能永远不需要执行完整的GC。一个健康的应用程序预计经常GC 在非常短的时间内收集Eden,所以不要被垃圾收集器踢得太过分心。但是请关注Full GC 。如果你还没有得到垃圾收集器日志现在是时候这样做,因为这将允许你衡量,如果你已经做了假设。

-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintTenuringDistribution 

另请参阅this answer了解详情。

从Java 7 update 4中,您可以使用G1垃圾收集器。此算法专门设计用于处理多核计算机上的大堆。当您启动应用程序时,您将使用类似这样的命令:

java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200

这允许您指定堆大小和要实现的目标性能。 G1的工作方式与默认使用的标记和扫描算法略有不同,因为它在不同区域的堆中查看,并尝试声明大部分垃圾进入的区域。堆的完全可回收段很容易回收,所以它会找到尽可能多的尽可能在你想要的暂停时间内收集它们。它尽可能多地利用你的多核来避免在停止世界时浪费时间。

G1 Garbage Collector

当然不能保证G1会成为您问题的灵丹妙药,但是根据您的特点,您可能会发现它非常适合您(因为缓存中不需要的东西可以更容易删除,并且可能由于当地原则而紧密相连。关于G1 here有一些初学者信息。另外,请看一下我在这里进一步深入的article

答案 1 :(得分:1)

我知道这是一个老问题,但我认为如果您的应用程序使用了大量缓存,那么您可以从较大的年轻代中获益。出于某种原因,这很少被推荐,但我在缓存用例中有一些非常好的结果。物体死亡的最佳位置是年轻一代。在年轻人中收集物品基本上没有成本。可能发生的最糟糕的事情是,你有足够长的物体移动到终身空间,然后迅速死亡。

在热点中有两种方法。

  1. 增加期限阈值:这是在提升对象之前的次要GC周期数,使这些对象有更多时间在年轻一代死亡
  2. 增加年轻一代的规模。次要收集的发生频率会降低,因此在年轻一代中有更多时间让对象死亡。
  3. 如果你有很多记忆可以玩,我会尝试为年轻一代分配一大笔钱。尝试给年轻一代至少两倍于你期望在缓存中活着的时间。首先将暂定阈值保留为2。

    使用这样的设置,我们已经运行了一个多月的缓存服务器(Infinispan)而没有主要的收藏。次要收集大约每小时运行一次,最大停顿时间为0.2秒,以收集6-7 GB的垃圾。在任何给定时间,现场设置约为1GB。它的实时设置主要决定了它所需的时间,因此进一步增加年轻一代将减少收集,但不应该更改暂停时间。

    在除缓存之外做其他事情的应用程序中,这可能无法完美地运行。在我的情况下,终身空间保持稳定在120MB左右。理想情况下,您将拥有足够的空间容纳您的长寿物品,并且只需将物品移至终身一次。根据应用程序的不同,这可能很难实现。

答案 2 :(得分:0)

我尝试用新的大小,堆大小调整JVM播放... 还尝试了G1(-XX:MaxGCPauseMillis = 200)但没有成功,很快就获得了OOM。

最终解决方案是仅加载更改。 仅当负载改变时,GC暂停时间仅为0.55%,平均暂停时间为17.3ms。

当加载所有数据时,你没有并发问题,因为你不需要在更新时锁定服务类结构(四元组,地图),因为你创建了一个新的服务类,并且一旦新服务类准备就绪为您提供更新服务类的引用服务。 为了仍然避免锁定服务数据,我们基于旧数据创建一个新的服务类并将更新的数据合并到其中,因此99.1%之前加载并存在于旧一代中的数据不需要是干净。 gc将仅清除四叉树引用和映射键引用,它将由CMS清除。

如果您必须完全刷新数据,请不要一次性完成。尝试将您的数据分解为少数结构,并且偶尔刷新每个数据结构,在这种情况下,GC将遭受更少的暂停时间和昂贵的故障。

如果你必须一次性完成,并且你可以承受长时间的停顿而不是创建一个小的新闻,那么该对象将传递给旧的。

在我们的测试中,有6个线程的恒定负载,每3分钟我们加载3.5G的服务数据并替换旧的数据。 完成的测试: -XX:NewSize = 6g -Xms20g -Xmx20g -XX:+ UseConcMarkSweepGC

收率: GC 6G newsize

GC的发生频率较低(收集之间的平均间隔(ms)3735),但大多数时间约为4秒。

更改为: -XX:CMSInitiatingOccupancyFraction = 70 -Xms20g -Xmx20g -XX:+ UseConcMarkSweepGC

收率: GC with small new size