【问题标题】:How do I solve a "java.lang.OutOfMemoryError: Java heap space"?如何解决“java.lang.OutOfMemoryError:Java 堆空间”?
【发布时间】:2011-11-25 02:20:05
【问题描述】:

我正在编写一些代码来将一个非常大的平面文本文件解析为持久保存到数据库的对象。这适用于文件的某些部分(即,如果我“置顶”前 2000 行),但当我尝试处理完整文件时遇到java.lang.OutOfMemoryError: Java heap space 错误。

我正在使用 BufferedReader 逐行读取文件,我的印象是这否定了将整个文本文件加载到内存中的要求。希望我的代码是不言自明的。我通过 Eclipse Memory Analyser 运行了我的代码,它告诉我:

线程 java.lang.Thread @ 0x27ee0478 main 保存总大小为 69,668,888 (98.76%) 字节的局部变量。
内存在“”**

加载的“char[]”的一个实例中累积

非常感谢有帮助的 cmets!

乔纳森

public ArrayList<Statement> parseGMIFile(String filePath)
            throws IOException {

        ArrayList<Statement> statements = new ArrayList<Statement>();

        // Statement Properties
        String sAccount = "";
        String sOffice = "";
        String sFirm = "";
        String sDate1 = "";
        String sDate2 = "";
        Date date = new Date();
        StringBuffer sData = new StringBuffer();
        BufferedReader in = new BufferedReader(new FileReader(filePath));
        String line;
        String prevCode = "";
        int lineCounter = 1;
        int globalLineCounter = 1;

        while ((line = in.readLine()) != null) {

                // We extract the GMI code from the end of the first line
                String newCode = line.substring(GMICODE_START_POS).trim();

                // Extract date
                if (newCode.equals(prevCode)) {

                    if (lineCounter == DATE_LINE) { 
                        sDate1 = line.substring(DATE_START_POS, DATE_END_POS).trim();}

                    if (lineCounter == DATE_LINE2) {
                        sDate2 = line.substring(DATE_START_POS, DATE_END_POS).trim();}

                    if (sDate1.equals("")){
                        sDate1 = sDate2;}
                        SimpleDateFormat formatter=new SimpleDateFormat("MMM dd, yyyy");
                        try {
                            date=formatter.parse(sDate1);

                        } catch (ParseException e) {

                            e.printStackTrace();
                        }                   



                    sFirm = line.substring(FIRM_START_POS, FIRM_END_POS);
                    sOffice = line.substring(OFFICE_START_POS, OFFICE_END_POS);
                    sAccount = line.substring(ACCOUNT_START_POS,
                            ACCOUNT_END_POS);
                    lineCounter++;
                    globalLineCounter++;
                    sData.append(line.substring(0, END_OF_DATA)).append("\n");

                } else {

                    // Instantiate New Statement Object
                    Statement stmt = new Statement(sAccount, sOffice, sFirm,
                            date, sData.toString());


                    // Add to collection
                    statements.add(stmt);

                    // log.info("-----------NEW STATEMENT--------------");
                    sData.setLength(0);
                    lineCounter = 1;
                }
                prevCode = newCode;
        }
        return statements;
    }
STACKTRACE:线程“main”org.springframework.beans.factory.BeanCreationException 中的异常:在类路径资源 [app-context.xml] 中定义名称为“dbPopulator”的 bean 创建错误:调用 init 方法失败;嵌套异常是 java.lang.OutOfMemoryError: Java heap space 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1401) 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:512) 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:450) 在 org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:290) 在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) 在 org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:287) 在 org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:189) 在 org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:557) 在 org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:842) 在 org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:416) 在 org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) 在 org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:93) 在 Main.main(Main.java:11) 引起:java.lang.OutOfMemoryError: Java heap space 在 java.util.Arrays.copyOf(Arrays.java:2882) 在 java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100) 在 java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390) 在 java.lang.StringBuffer.append(StringBuffer.java:224) 在 services.GMILogParser.parseGMIFile(GMILogParser.java:133) 在 services.DBPopulator.init(DBPopulator.java:27) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 在 java.lang.reflect.Method.invoke(Method.java:597) 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1529) 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1468) 在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1398) ... 12 更多

【问题讨论】:

  • 如果您不在服务器虚拟机上,默认堆空间为 64 MB,您可以尝试将其增加到 512MB 或更多。

标签: java parsing out-of-memory


【解决方案1】:

也许是 statements 对象变得太大了?如果是这样,也许您应该将它分批而不是一次全部持久化到数据库中?

【讨论】:

  • 嗨丹尼尔,这是一个很好的建议。我现在一定是在创建一个巨大的 ArrayList。
  • 这将增加对数据库的需求,此时我们不知道应用程序是否包含该数据库。我们无论如何都应该从文件中插入数据库。
  • 但目的是插入数据库对吗?问题的第一行是:“我正在编写一些代码来将一个非常大的平面文本文件解析为持久保存到数据库中的对象。”
【解决方案2】:

您的应用程序似乎正在使用 VM 分配的默认内存(如果我没记错的话,大约是 64 MB)。由于您的应用程序是一个特殊用途的应用程序,我建议增加应用程序的可用内存(例如,使用java -Xmx256m 运行应用程序将允许它使用多达 256 MB 的 RAM)。您也可以尝试使用服务器 VM (java -server yourapp) 运行它,这将尝试优化一些东西。

【讨论】:

  • 但这只能在使用 256MB 之前解决问题,对吧?找到使用这么多内存的内容可能会更好,然后例如分批进行持久性(正如我在回答中尝试做的那样)。
  • @DanielLundmark 是的,当然。我当然同意,你应该寻找瓶颈。但这也有助于使用更多内存;恕我直言,64 MB 的缓冲区并不大。例如,对于此类特定用途的应用程序,我不介意使用 1 GB 缓冲区,特别是如果它要运行一次左右。
【解决方案3】:

在启动参数中添加更多内存是恕我直言的错误。这些参数是应用范围的。并且可能会通过增加gc 倍来进行惩罚。此外,您可能事先不知道尺寸。

您使用MemoryMappedFiles 并查看java.nio.* 来执行此操作。这样做可以边读边加载,内存不放在普通内存空间中。

通过在低级别阅读,您可以以可变长度的块进行阅读。而且速度很重要。如果您的文件很大,则可能需要花费太多时间来阅读它。并且您在JVM 中存储的Objects 的数量使GC 工作并且应用程序变慢。 来自java参考:

  • byte buffer 可以分配为直接缓冲区,在这种情况下,Java 虚拟机将尽最大努力直接在其上执行 native I/O operations

  • 可以通过将文件的一个区域直接映射到内存来创建byte buffer,在这种情况下,可以使用 MappedByteBuffer 类中定义的一些额外的文件相关操作。

  • byte buffer 提供对其内容的访问,可以是任意非布尔基元类型的异构或同类二进制数据序列,采用大端或小端字节序。

【讨论】:

  • 我今天的菜鸟问题来了:D.... nio 的表现不比普通的旧 IO 差吗?或者只是与多线程环境有关?我记得读过一些与 io 如何超越 nio 相关的内容,但找不到参考链接。
  • 好的,找到了我所指的文章。 thebuzzmedia.com/java-io-faster-than-nio-old-is-new-again
【解决方案4】:

代码对我来说似乎是正确的。也许我应该用 StringBuffer 代替 String。

String 在 java 中非常讨厌,每次对它们进行修改时,都会创建一个新对象,并且 ref 可以保留在代码中的任何位置。

通常我使用本地变量读取私有方法中的文件行,只是为了确保没有留下对 String 的引用。

您返回的列表是具有 String 属性的 bean 列表?如果是这样,请将它们更改为 StringBuffer 并重新运行分析。

如果这对你有帮助,请告诉我。

问候,

M.

【讨论】:

  • 我在更改代码时遇到了一些问题,以便将“语句”对象添加到 ArrayList 中,它只是将其传递给将其插入数据库的数据库服务对象。跨度>
  • 听起来不错,但要注意性能问题。通常批处理语句比单个语句的重复执行得更快。如果性能下降,您可以尝试混合解决方案(使用一个包含 n 条语句的窗口一起传递给 Db 服务)
【解决方案5】:

这里可能发生的另一件事: 如果您的文件大于堆的一半并且不包含任何换行符 in.readLine() 将尝试读取整个文件并在这种情况下失败。

【讨论】:

    【解决方案6】:

    似乎是 sData 导致了溢出。文本中应该有几个(百万?)语句具有相同的 GMI 代码。

    char[] 的累积表示 String 或 StringBuilder。由于调整StringBuilder大小失败,应该是原因。

    只需尝试将 sData 输出到标准输出进行调试,看看会发生什么。

    【讨论】:

    • 是的,我想是的。这特别有趣,因为据我所知,sData 中的字符串没有被使用。
    • @Ingo 用于累积语句的字符串表示。一旦语句结束,它就用于构建语句: Statement stmt = new Statement(sAccount, sOffice, sFirm, date, sData.toString());
    • 在日记账上的交易结算 CC 借/贷 ------- ------- -- ----- ------------- -- -------- 9/21/1 9/21/1 EH 2494747 /EQY/ SCL/1171 HK Equ/ HD 803,808.60DR 0 /CNE1000004Q8/6109893 9/21/1 9/21/1 EH 2494748
    • 基本上,它正在工作......并且当我在每次循环迭代结束时将 sData 重置为 0 时(不包括那么多行),为什么这会导致溢出?跨度>
    • 虽然,当我注释掉将数据附加到 sData 中的代码时,脚本能够毫无问题地解析整个 1.2GB 文件。
    【解决方案7】:

    -Xmx1024M -XX:MaxPermSize=256M 解决了我的java.lang.OutOfMemoryError: Java heap space error

    希望这会奏效。

    【讨论】:

      【解决方案8】:

      您为什么不尝试替换该行(如果您使用JDK 6,则在JDK 7中解决了子字符串内存问题)

      String newCode = line.substring(GMICODE_START_POS).trim();
      

      换行:

      String newCode = new String(line.substring(GMICODE_START_POS));
      

      【讨论】:

        【解决方案9】:

        几个月前我遇到了同样的问题

        我用Scanner类:

        Scanner scanner = new Scanner(file);
        

        代替:

        BufferedReader in = new BufferedReader(new FileReader(filePath));
        

        【讨论】:

          猜你喜欢
          • 2010-12-08
          • 2015-05-14
          • 2019-07-10
          • 2019-12-12
          相关资源
          最近更新 更多