Java内存溢出异常

7

我正在Tomcat中运行一个Java Web应用程序。该应用程序使用Quartz框架定期安排cron作业。这个cron作业涉及解析一个4+ MB的xml文件,我使用JDOM API来完成。xml文件包含大约3600个节点需要解析,随后数据需要按顺序更新到数据库中。


在解析了近一半的文件后,我的应用程序抛出了内存不足异常。其堆栈跟踪如下:

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。
谢谢, Amit

4
不要忘记为这个问题和之前的问题选择一个答案;你已经提出了7个问题,但没有一个得到了满意的答案? - Rubens Farias
9个回答

10

尝试增加Java虚拟机的内存分配。这应该会有所帮助。

Eclipse的解决方案:可以通过以下方法在Eclipse的首选项中配置:

  1. Windows -> Preferences(对于Mac电脑,是eclipse -> preferences)
  2. Java -> Installed JREs
  3. 选择JRE并单击“编辑”
  4. 在默认的VM参数字段中键入-Xmx1024M(或您的内存偏好,例如1 GB的RAM为1024)
  5. 单击完成或“确定”按钮。

5
每次使用DOM解析XML文件时,都会将整个文件加载到内存中,并且DOM基础设施将使用大约相同的大小来处理它,因此它将消耗大约两倍于文件大小的内存。
您需要使用SAX,一种基于事件的解析器。虽然第一次理解可能很困难,但它非常节省内存,因为它只保留当前解析节点在内存中。
似乎Java有一些SAX实现,例如StAX,希望能有所帮助。

嗨,Rubens,我正在使用 JDOM 解析大型 XML,它在内部使用 SAX 解析器。我的解析代码是: SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(inputResource); Element elem = doc.getRootElement(); - Amit
1
由于您的DOM解析器使用SAX,因此您应该按顺序读取XML,并避免使用“..”、“//”等内容。 - Rubens Farias
StAX和SAX是不同的API。然而,它们都可以用于减少内存使用量。(SAX使用回调,在StAX中,用户代码请求下一个解析标记) - Petr Gladkikh

3
尝试增加JVM的内存分配,这应该会有所帮助。
Eclipse解决方法:
您可以在Eclipse的首选项中进行配置,具体如下:
Windows -> Preferences(Mac上为:Eclipse -> Preferences)Java -> Installed JREs
选择JRE并单击默认VM参数字段上的Edit按钮,在其中键入-Xms256m -Xmx512m -XX:MaxPermSize=512m -XX:PermSize=128m(或您的内存偏好,对于1 GB RAM为1024)。单击完成或确定。

3
解析XML是一项相当昂贵的任务。平均DOM解析器所需的内存空间至少是XML文档大小的五倍。你也应该考虑到这一点。为了确保没有其他地方导致了XML解析器的内存不足,你真的需要运行分析器。给它更多的内存,将可用内存翻倍并进行分析。当你找到原因并修复了泄漏后,你可以回退到“默认”内存并重新测试。或者如果确实没有任何泄漏的方法,那么就给它们所有人比默认内存多一些,以使其适合。
你还可以考虑使用更节省内存的XML解析器,例如VTD-XML主页在这里基准测试在这里)。

2
你需要为Tomcat的JVM分配更多的PermGenSpace空间。
可以通过JVM参数-XX:MaxPermSize=128m来实现。
默认情况下,PermGen空间为64M(其中包含所有已编译的类,因此如果您的类路径中有很多jar(类)文件,则可能会填满此空间)。
另外,你可以使用JVisualVM监控PermGen空间的大小,甚至可以使用YourKit Java Profiler检查其内容。

1

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

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


嗨,Jason,感谢您的回复。我增加了堆大小,应用程序运行得非常好。我已经为我的应用程序设置了分析,并在完成过程后发现两种未被垃圾收集器释放的活动分配对象,它们是org.postgresql.jdbc4.Jdbc4PrepareStatement和org.postgresql.jdbc4.Jdbc4ResultSet。这些可能是应用程序中内存泄漏的原因吗? - Amit
这可能表明您没有正确关闭JDBC对象。您是自己进行JDBC调用还是使用框架(例如Spring)来包装JDBC调用?如果您直接调用JDBC,请确保在finally块中调用任何ResultSet、Statement、PreparedStatement和Connection对象的close()方法,以便在使用完毕后关闭它们。 - Jason Gritman
尽我所能,我已经确保关闭了我打开的所有连接,但问题仍然存在。你能帮我解决这个问题吗? - Amit

0

我同意文件和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的内部操作。 - BalusC
嗨Duffy,感谢您的回复。JDOM内部是否可以实现此复制?我始终为应用程序启用了分析,并且即使在GC运行后仍存在2个类,它们是org.postgresql.jdbc4.Jdbc4PrepareStatement和org.postgresql.jdbc4.Jdbc4ResultSet。这些可能是应用程序中内存泄漏的原因吗? - Amit
可能是。如果没有代码,就无法确定,但是如果您没有正确关闭这些资源,可能会导致资源泄漏,给您带来麻烦。 - duffymo
尽我所能,我已经确保关闭了我打开的所有连接,但问题仍然存在。你能帮我解决这个问题吗? - Amit

0

你确定没有递归数组复制的问题吗?可能是在不同的线程中留下的错误吗?


嗨,Lorenzog,为此我没有使用任何线程,也没有使用数组拷贝。我正在使用JDOM来解析XML文件,我猜它在实现中使用了ArrayList。这可能会有问题吗?是否存在内存泄漏的可能性? - Amit

0

您可以使用以下命令运行应用程序:-XX:+HeapDumpOnOutOfMemoryError。这将导致JVM在内存耗尽时生成堆转储。然后,您可以使用MAT或JHAT之类的工具查看占用内存的对象。建议使用生成的堆转储上的Eclipse Memory Analyzer工具(MAT),因为它非常易于使用:http://www.eclipse.org/mat/

当然,您需要大概知道哪些对象可能挂起以便于使用此方法。DOM对象?先前加载的XML文档中的资源?数据库连接?MAT将允许您跟踪引用到根对象,从某个您认为应该被垃圾收集器回收的对象开始。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接