减少内存流失的方法

时间:2010-06-19 20:37:53

标签: java memory-management garbage-collection spring-batch

背景

我有一个Spring批处理程序,它读取一个文件(我正在使用的示例文件大小约为4 GB),对该文件进行少量处理,然后将其写入Oracle数据库。

我的程序使用1个线程来读取文件,使用12个工作线程来执行处理和数据库推送。

我正在搅拌很多很多年轻的生成记忆,导致我的程序比我想象的要慢。

设置

JDK 1.6.18
春季批次2.1.x
4核心机器16 GB RAM

-Xmx12G 
-Xms12G 
-NewRatio=1 
-XX:+UseParallelGC
-XX:+UseParallelOldGC

问题

通过这些JVM参数,我可以为Tenured Generation提供大约5.x GB的内存,为Young Generation提供大约5.X GB的内存。

在处理这个文件的过程中,我的Tenured Generation很好。它增长到最大可能3 GB,我从来不需要做一个完整的GC。

然而,年轻一代最多次击中它。它高达5 GB范围,然后发生并行次要GC并将Young Gen清除至500MB。 Minor GCs比完整的GC更好,但是它仍然会减慢我的程序(我很确定当年轻的gen集合发生时应用程序仍然会冻结,因为我看到数据库活动已经消失)。我花费超过5%的计划时间冻结为小型GC,这似乎过多。我会说在处理这个4 GB文件的过程中,我会通过50-60GB的年轻生成记忆

我的程序中没有任何明显的缺陷。我试图遵守一般的OO原则并编写干净的Java代码。我试图不要无缘无故地创建对象。我正在使用线程池,并且尽可能传递对象而不是创建新对象。我将开始分析应用程序,但是我想知道是否有人有一些好的一般经验法则或反模式以避免导致过多的内存流失? 50-60GB的内存流失是否可以处理4GB文件?我是否必须恢复到像对象池这样的JDk 1.2技巧? (尽管Brian Goetz做了一个演示文稿,其中包括为什么对象池是愚蠢的,我们不再需要这样做。我相信他比我更信任自己...... :))

7 个答案:

答案 0 :(得分:9)

我觉得你花时间和精力试图优化你不应该打扰的东西。

  

我花费超过5%的节目时间被冻结用于较小的GC,这似乎过度。

翻转一下。你花费不到95%的课程时间做有用的工作。换句话说,即使您设法优化GC以在零时间内运行,您可以获得的最佳效果是超过5%。

如果您的应用程序具有受暂停时间影响的硬时序要求,则可以考虑使用低暂停收集器。 (请注意,减少暂停时间会增加整体GC开销......)但是对于批处理作业,GC暂停时间不应该相关。

最重要的是整个批处理作业的挂钟时间。并且(大约)95%的时间用于执行应用程序特定的工作,您可能会获得更多的分析/目标优化工作的回报。例如,您是否看过批量发送到数据库的更新?


  

所以..我总内存的90%在“oracle.sql.converter.toOracleStringWithReplacement”中的char []中

这可能表示在准备要发送到数据库的内容时,大多数内存使用都发生在Oracle JDBC驱动程序中。关于那个你很少。我把它当成不可避免的开销。

答案 1 :(得分:3)

如果你澄清你的术语“年轻”和“试探”一代将会非常有用,因为Java 6的GC模型略有不同:Eden,S0 + S1,Old,Perm

您是否尝试过不同的垃圾收集算法?如何执行“UseConcMarkSweepGC”或“UseParNewGC”。

并且不要忘记简单地增加可用空间不是解决方案,因为gc运行需要更长时间,将大小减小到正常值;)

您确定没有内存泄漏吗?在您描述的消费者 - 生产者模式中 - 旧Gen中很少有数据应该存在,因为这些工作的执行速度非常快,然后“被抛弃”,或者您的工作队列是否填满?

您应该使用内存分析器彻底观察您的程序。

答案 2 :(得分:2)

我认为与内存探查器的会话将对此主题有很多启示。这样可以很好地概述创建了多少个对象,这很有趣。

我总是惊讶于生成了多少个字符串。

对于域对象,交叉引用它们也很有启发性。如果您突然看到来自派生对象的对象多于来自源的对象的3倍,那么就会发生一些事情。

Netbeans有一个很好的建立它。我过去使用过JProfiler。我想如果你在日食上敲得足够长,你可以从PPTP工具中获得相同的信息。

答案 3 :(得分:2)

您需要对应用程序进行概要分析,以了解具体情况。我还会首先尝试使用JVM的人体工程学功能,如下所示:

  

2. Ergonomics

     

此处称为的功能   人机工程学是在J2SE 5.0中引入的。   人体工程学的目标是提供   良好的性能,很少或没有   通过调整命令行选项   选择

     
      
  • 垃圾收集器,
  •   
  • 堆大小,
  •   
  • 和运行时编译器
  •   
     

在JVM启动时,而不是使用固定的   默认值。此选择假设为   机器的类   应用程序运行是一个暗示   应用的特点   (即大型应用程序在大型应用程序上运行   机)。除了这些   选择是一种简化的方式   调整垃圾收集。随着   用户可以并行收集器   指定最长暂停时间的目标   以及所需的吞吐量   应用。这与之形成鲜明对比   指定堆的大小   需要良好的表现。这个   旨在特别改进   大型应用程序的性能   使用大堆。越一般   人体工程学描述于   题为“人体工程学”的文件   5.0 Java虚拟机“。 建议将人体工程学设为   在后一份文件中提出   尝试之前使用更详细的   本文档中解释的控件

     

本文件包括   人体工程学功能作为一部分提供   的自适应规模政策   并行收集器。这包括   指定目标的选项   垃圾收集和性能   微调的其他选项   性能

请参阅Ergonomics指南中有关Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning的更详细部分。

答案 4 :(得分:1)

在我看来,年轻一代不应该像老一代那样大,所以小垃圾收集的速度很快。

你有很多代表相同价值的物品吗?如果这样做,请使用简单的HashMap

合并这些重复的对象
public class MemorySavingUtils {

    ConcurrentHashMap<String, String> knownStrings = new ConcurrentHashMap<String, String>();

    public String unique(String s) {
        return knownStrings.putIfAbsent(s, s);
    }

    public void clear() {
        knownStrings.clear();
    }
}

使用Sun Hotspot编译器,对于大量字符串,本机String.intern()非常慢,这就是我建议构建自己的String interner的原因。

使用此方法,可以重用旧一代的字符串,并且可以快速收集新一代的字符串。

答案 5 :(得分:1)

  

从文件中读取一行,以字符串形式存储并放入列表中。当列表包含1000个这些字符串时,将其放入队列以供工作线程读取。说工作线程创建域对象,从字符串中剥离一堆值来设置字段(int,long,java.util.Date或String),并将域对象传递给默认的spring批处理jdbc writer < / p>

如果这是你的程序,为什么不设置较小的内存大小,如256MB?

答案 6 :(得分:1)

我猜测内存限制很高,你必须在进行处理之前将文件完全读入内存。你能考虑改用java.io.RandomAccessFile吗?