【问题标题】:Java out of memory ExceptionJava内存不足异常
【发布时间】:2010-12-21 13:56:38
【问题描述】:

我正在 Tomcat 中运行 Java Web 应用程序。该应用程序使用 Quartz 框架定期安排 cron 作业。此 cron 作业涉及解析一个 4+ MB 的 xml 文件,我正在使用 JDOM API 进行此操作。 xml 文件包含大约 3600 个要解析的节点,因此要在 DB 中更新数据,我正在按顺序进行。
在解析了几乎一半的文件后,我的应用程序抛出了内存不足异常。相同的堆栈跟踪是:

Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOfRange(Arrays.java:3210)
        at java.lang.String.<init>(String.java:216)
        at java.lang.StringBuffer.toString(StringBuffer.java:585)
        at org.netbeans.lib.profiler.server.ProfilerRuntimeMemory.traceVMObjectAlloc(ProfilerRuntimeMemory.java:170)
        at java.lang.Throwable.getStackTraceElement(Native Method)
        at java.lang.Throwable.getOurStackTrace(Throwable.java:590)
        at java.lang.Throwable.getStackTrace(Throwable.java:582)
        at org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:155)
        at org.apache.juli.logging.DirectJDKLog.error(DirectJDKLog.java:135)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1603)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1610)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1590)
        at java.lang.Thread.run(Thread.java:619)
Exception in thread "*** JFluid Monitor thread ***" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2760)
        at java.util.Arrays.copyOf(Arrays.java:2734)
        at java.util.Vector.ensureCapacityHelper(Vector.java:226)
        at java.util.Vector.add(Vector.java:728)
        at org.netbeans.lib.profiler.server.Monitors$SurvGenAndThreadsMonitor.updateSurvGenData(Monitors.java:230)
        at org.netbeans.lib.profiler.server.Monitors$SurvGenAndThreadsMonitor.run(Monitors.java:169)
Nov 30, 2009 2:22:05 PM org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor processChildren
SEVERE: Exception invoking periodic operation:
java.lang.OutOfMemoryError: Java heap space
        at java.lang.StringCoding$StringEncoder.encode(StringCoding.java:232)
        at java.lang.StringCoding.encode(StringCoding.java:272)
        at java.lang.String.getBytes(String.java:946)
        at java.io.UnixFileSystem.getLastModifiedTime(Native Method)
        at java.io.File.lastModified(File.java:826)
        at org.apache.catalina.startup.HostConfig.checkResources(HostConfig.java:1175)
        at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1269)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:296)
        at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:118)
        at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1337)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1601)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1610)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1590)
        at java.lang.Thread.run(Thread.java:619)
ERROR [JobRunShell]: Job updateVendorData.quoteUpdate threw an unhandled Exception:
java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOfRange(Arrays.java:3210)
        at java.lang.String.<init>(String.java:216)
        at java.lang.StringBuffer.toString(StringBuffer.java:585)
        at org.apache.commons.dbcp.PoolingConnection$PStmtKey.hashCode(PoolingConnection.java:296)
        at java.util.HashMap.get(HashMap.java:300)
        at org.apache.commons.pool.impl.GenericKeyedObjectPool.decrementActiveCount(GenericKeyedObjectPool.java:1085)
        at org.apache.commons.pool.impl.GenericKeyedObjectPool.returnObject(GenericKeyedObjectPool.java:882)
        at org.apache.commons.dbcp.PoolablePreparedStatement.close(PoolablePreparedStatement.java:80)
        at org.apache.commons.dbcp.DelegatingStatement.close(DelegatingStatement.java:168)
        at com.netcore.smsapps.stock.db.CompanyDaoImpl.updateCompanyQuote(CompanyDaoImpl.java:173)
        at com.netcore.smsapps.stock.vendor.MyirisVendor.readScripQuotes(MyirisVendor.java:159)
        at com.netcore.smsapps.stock.update.StockUpdateData.execute(StockUpdateData.java:38)
        at org.quartz.core.JobRunShell.run(JobRunShell.java:207)
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:525)
DEBUG [ExceptionHelper]: Detected JDK support for nested exceptions.
ERROR [ErrorLogger]: Job (updateVendorData.quoteUpdate threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.OutOfMemoryError: Java heap space]
        at org.quartz.core.JobRunShell.run(JobRunShell.java:216)
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:525)
Caused by: java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOfRange(Arrays.java:3210)
        at java.lang.String.<init>(String.java:216)
        at java.lang.StringBuffer.toString(StringBuffer.java:585)
        at org.apache.commons.dbcp.PoolingConnection$PStmtKey.hashCode(PoolingConnection.java:296)
        at java.util.HashMap.get(HashMap.java:300)
        at org.apache.commons.pool.impl.GenericKeyedObjectPool.decrementActiveCount(GenericKeyedObjectPool.java:1085)
        at org.apache.commons.pool.impl.GenericKeyedObjectPool.returnObject(GenericKeyedObjectPool.java:882)
        at org.apache.commons.dbcp.PoolablePreparedStatement.close(PoolablePreparedStatement.java:80)
        at org.apache.commons.dbcp.DelegatingStatement.close(DelegatingStatement.java:168)
        at com.netcore.smsapps.stock.db.CompanyDaoImpl.updateCompanyQuote(CompanyDaoImpl.java:173)
        at com.netcore.smsapps.stock.vendor.MyirisVendor.readScripQuotes(MyirisVendor.java:159)
        at com.netcore.smsapps.stock.update.StockUpdateData.execute(StockUpdateData.java:38)
        at org.quartz.core.JobRunShell.run(JobRunShell.java:207)

这甚至会导致我的 tomcat 崩溃。你能帮我诊断问题吗?我什至在 Netbeans 中启用了相同的分析,但似乎即使这样也崩溃了。我保留了分配给 Tomcat 的默认内存。是否发生任何内存泄漏。 我的数据库是 postgres,JDK 是 1.6.0_15。

谢谢, 阿米特

【问题讨论】:

  • 不要忘记为这个问题和以前的问题选择一个答案;您已经提出了 7 个问题,但没有一个给出好的答案?

标签: java tomcat xml-parsing quartz-scheduler jdom


【解决方案1】:

尝试增加 JVM 的内存分配。它应该会有所帮助。

Eclipse 修复

您可以在 Eclipse 的首选项中进行如下配置:

Windows -> 首选项(在 Mac 上是:Eclipse -> 首选项)Java -> 已安装的 JRE

选择 JRE 并在-Xms256m -Xmx512m -XX:MaxPermSize=512m -XX:PermSize=128m 中的默认 VM 参数字段类型上单击编辑(或您的内存偏好,对于 1 gb 内存,它是 1024)。点击完成或确定。

【讨论】:

    【解决方案2】:

    尝试增加 JVM 的内存分配。它应该会有所帮助。

    修复 Eclipse:您可以在 Eclipse 首选项中进行如下配置

    1. Windows -> 首选项(在 mac 上为:eclipse -> 首选项)
    2. Java -> 已安装的 JRE
    3. 选择 JRE 并单击编辑
    4. 在 -Xmx1024M 中的默认 VM 参数字段类型上。 (或您的记忆偏好,对于 1 GB 的 ram 其 1024)
    5. 点击完成或确定。

    【讨论】:

      【解决方案3】:

      你必须为 tomcat JVM 的 PermGenSpace 分配更多空间。

      这可以通过 JVM 参数来完成:-XX:MaxPermSize=128m

      默认情况下,PermGen 空间是 64M(它包含所有编译的类,所以如果你的类路径中有很多 jar(类),你可能确实会填满这个空间)。

      附带说明,您可以使用JVisualVM 监控 PermGen 空间的大小,甚至可以使用YourKit Java Profiler 检查其内容

      【讨论】:

        【解决方案4】:

        您可以使用以下命令运行您的应用程序:-XX:+HeapDumpOnOutOfMemoryError。这将导致 JVM 在内存不足时产生堆转储。您可以使用类似:MAT 或 JHAT 来查看正在持有哪些对象。我建议在生成的堆转储上使用 eclipse 内存分析器工具 (MAT),因为它使用起来相当简单:http://www.eclipse.org/mat/

        当然,您需要对可能悬挂的对象有所了解才能使其有用。 DOM 对象?来自以前加载的 xml 文档的资源?数据库连接? MAT 将允许您从某个您怀疑应该被垃圾回收的对象中追踪对根对象的引用。

        【讨论】:

          【解决方案5】:

          解析 XML 是一项相当昂贵的任务。与 XML 文档大一样,平均 DOM 解析器至少需要 倍的内存空间。你也应该考虑到这个事实。为了确保其他地方没有导致 XML 解析器内存不足的内存泄漏,您确实需要运行分析器。给它更多内存,将可用内存加倍并对其进行配置。当您确定原因并修复泄漏后,您可以退回到“默认”内存并重新测试。或者,如果真的没有任何泄漏的方法,那么只需给它比默认更多的内存,这样就可以了。

          您也可以考虑使用内存效率更高的 XML 解析器,例如 VTD-XML (homepage here, benchmarks here)。

          【讨论】:

            【解决方案6】:

            您是否尝试将最大堆大小设置得更大,以查看问题是否仍然存在?甚至可能根本没有泄漏。可能只是默认堆大小(我认为在 Windows 上为 64m)对于这个特定进程来说是不够的。

            我发现我几乎总是需要为运行 Tomcat 的任何应用程序提供比默认设置更多的堆和永久生成空间,否则我会遇到内存不足的问题。如果您需要帮助调整内存设置,请查看this question

            【讨论】:

            • 嗨,杰森,感谢您的回复。我增加了堆大小,应用程序运行良好。我已经为我的应用程序设置了分析,并且在该过程完成后,可以找到 2 种没有被垃圾收集器释放的实时分配对象,它们是 org.postgresql.jdbc4.Jdbc4PrepareStatement 和 org.postgresql.jdbc4.Jdbc4ResultSet。这些可能是导致应用程序内存泄漏的原因吗?
            • 这可能表明您没有正确关闭您的 JDBC 对象。您是自己进行 JDBC 调用还是使用框架(如 Spring)来包装 JDBC 调用?如果您直接调用 JDBC,请确保在使用完后,在 finally 块中对任何 ResultSet、Statement、PreparedStatement 和 Connection 对象调用 close() 方法。
            • 尽我所能,我已关闭所有已打开的连接,但问题仍然存在。有什么办法可以帮助我解决这个问题
            【解决方案7】:

            关于文件和 DOM 占用大量内存,我将再次强调这一点。我也想知道什么时候看到这个:

            ERROR [JobRunShell]: Job updateVendorData.quoteUpdate threw an unhandled Exception:  
                java.lang.OutOfMemoryError: Java heap space
                at java.util.Arrays.copyOfRange(Arrays.java:3210)
            

            复制是做什么的?我想知道您的代码中是否还有其他问题。

            如果您已经走到这一步,则表明您已成功读取文件和 DOM,并且您正在开始写入数据库。文件内存应该已经被回收了。

            我建议使用VisualGC 查看内存,以便您了解发生了什么。

            【讨论】:

            • 复制是StringBuffer的内部结构。
            • 嗨达菲,感谢您的回复。 JDOM 可以在内部实现这种复制吗?始终我为应用程序启用了分析,即使在 GC 运行后也存在 2 个类,它们是 org.postgresql.jdbc4.Jdbc4PrepareStatement 和 org.postgresql.jdbc4.Jdbc4ResultSet。这些可能是导致应用程序内存泄漏的原因吗?
            • 可能是。没有代码就无法判断,但如果你没有正确关闭这些,你可能会泄漏资源,这会给你带来痛苦。
            • 尽我所能,我已关闭所有已打开的连接,但问题仍然存在。有什么办法可以帮助我解决这个问题
            【解决方案8】:

            您确定某处没有递归数组副本,错误地留在那里吗?也许在不同的线程中?

            【讨论】:

            • 嗨 Lorenzog,我没有为此目的使用任何线程,也没有数组副本。我正在使用 JDOM 来解析 XML 文件,我猜它使用 ArrayList 来实现它。这会是个问题吗?有没有内存泄露的可能?
            【解决方案9】:

            每次使用 DOM 解析 XML 文件时,都会将整个文件加载到内存中,并且 DOM 基础架构将使用大致相同的大小来处理它,因此它会消耗大约两倍于文件大小的内存。

            您需要使用基于事件的解析器 SAX。虽然第一次可能很难理解,但它是一个非常有效的内存,因为它只是将当前解析节点保存在内存中。

            似乎 Java 有一些 SAX 实现,例如 StAX,希望对您有所帮助。

            【讨论】:

            • 嗨鲁本斯,我正在使用 JDOM 来解析大型 XML,它在内部使用 SAX 解析器。我的解析代码是: SAXBuilder builder = new SAXBuilder();文档 doc = builder.build(inputResource);元素 elem = doc.getRootElement();
            • 由于你的 DOM 解析器使用 SAX,你应该按顺序读取你的 XML,并避免使用..// 之类的东西
            • StAX 和 SAX 是不同的 API。但是,它们都可以用来减少内存使用。 (SAX 使用回调,在 StAX 用户代码中要求下一个解析的令牌)