sun jdk内存泄露解析之java.lang.OutOfMemoryError: Java heap space

本文通过两个实例深入分析了 Java 中 OutOfMemoryError 的产生原因及如何避免。介绍了如何利用工具分析内存使用情况,并给出了具体的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是最常见的OOM错误,当通过new创建对象或数组时,如Java Heap空间不足(新生代不足,触发Young GC,还是不够,触发Full GC,还是不够),则抛出此错误。

既然最常见,更要注意避免。让我们看几个出现这种OOM的示例:

1. 先看code

public  static  void main ( String [ ] args )  throws  Exception {
     new  Thread ( new  Runnable ( )  {
         public  void run ( )  {
            List <byte [ ] > listbytes = new ArrayList <byte [ ] > ( ) ;
             for  ( int i  =  0 ; i  <  4028 ; i ++ )  {
                 System. out. println ( "thread a: " +i ) ;
                listbytes. add ( new  byte [ 1024 ] ) ;
             }
         }
     } ). start ( ) ;
}

以 -Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC -XX:+ HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -Xloggc:gc.log 参数运行

运行后在输出的日志中可看到大量的Full GC信息,以及:java.lang.OutOfMemoryError: Java heap space

先看看20M的堆内存,怎么分配的:
Heap
PSYoungGen      total 8960K
eden space 7680K
from space 1280K
to   space 1280K
PSOldGen        total 10240K
object space 10240K
PSPermGen       total 12288K
object space 12288K

可以看到:
PSYoungGen(新生代)中,eden+from+to=10240K
total 8960K是因为新生代内存from和to两个space不可能同时使用,所以7680k+1280K=8960K
PSOldGen(老生代),10240K
至于PSPermGen,无视他吧,丫不属于GC管理的堆内存。

再观察一下gc.log中gc的情况,在0.1s左右做了一次Young GC之后,后边疯狂的Full GC(好吧,还不是非常疯狂,否则最后报错就不是Java heap space了,这个以后再说)。注意在Full GC的过程中,PSOldGen一直在上涨,因为“可达”,GC对PSOldGen的清理微不足道。一直上涨,也必然造成内存不足、泄露。

前边我们已经知道这种OOM是在new对象或数组时Java Heap Space不足造成的,现在需要知道的是程序中哪些部分占用了Java Heap。

通过mat来分析程序运行生成的hprof文件。通过dominator_tree,可看到sun.misc.Launcher$AppClassLoader占据了大部分的内存。继续点开,发现根源在一个ArrayList,其中存放的对象占据了大部分的内存:


怎样避免:

这种情况造成的OOM,在实际的场景中当使用集合时很容易产生,程序中对于所有集合(List、Map等等)、缓存(本地Cache)大小都应给定限制的最大大小,以避免出现集合或缓存太大,导致消耗了过多的内存,从而导致OOM

2. 再来一段代码先

public  static  void main ( String [ ] args )  throws  Exception {
     for  ( int i  =  0 ; i  <  10 ; i ++ )  {
         new  Thread ( new  Runnable ( )  {
             public  void run ( )  {
                 byte [ ] bytes  =  new  byte [ 1024 * 1024 * 2 ] ;
                 System. out. println ( Thread. currentThread ( ). getName ( ) ) ;
                 try  {
                     Thread. sleep ( 1000 ) ;
                 } catch  ( InterruptedException e )  {
                    e. printStackTrace ( ) ;
                 }
             }
         } ). start ( ) ;
     }
}

同样,以-Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC -XX:+ HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -Xloggc:gc.log参数运行

把生成的hprof文件放入mat中进行分析,通过dominator_tree,可以看到,是一堆线程瓜分了内存:


在这个例子中,每个线程分配 2M(1024*1024*2)的内存,并且,占用1s钟不释放。与此同时,其他线程又在马不停蹄的申请内存…所以,内存溢出了。

怎样避免:

这种情况主要出现在多线程系统中,并且每个线程占用内存比较大或者处理速度较慢。

有四种方法来解决这个问题:

  1. 减少处理的线程数
  2. 处理线程需要消耗的内存
  3. 提升线程的处理速度
  4. 增加机器,减少单台机器所需承担的请求量
java: java.lang.OutOfMemoryError: Java heap space java.lang.RuntimeException: java.lang.OutOfMemoryError: Java heap space at com.sun.tools.javac.main.Main.compile(Main.java:559) at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129) at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138) at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:239) at org.jetbrains.jps.javac.ExternalJavacProcess.compile(ExternalJavacProcess.java:189) at org.jetbrains.jps.javac.ExternalJavacProcess.access$400(ExternalJavacProcess.java:28) at org.jetbrains.jps.javac.ExternalJavacProcess$CompilationRequestsHandler$1.run(ExternalJavacProcess.java:262) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.OutOfMemoryError: Java heap space at java.io.BufferedInputStream.<init>(BufferedInputStream.java:203) at java.io.BufferedInputStream.<init>(BufferedInputStream.java:183) at org.jetbrains.jps.javac.ZipFileObject.openInputStream(ZipFileObject.java:48) at com.sun.tools.javac.api.ClientCodeWrapper$WrappedFileObject.openInputStream(ClientCodeWrapper.java:426) at com.sun.tools.javac.jvm.ClassReader.fillIn(ClassReader.java:2510) at com.sun.tools.javac.jvm.ClassReader.complete(ClassReader.java:2442) at com.sun.tools.javac.jvm.ClassReader.access$000(ClassReader.java:76) at com.sun.tools.javac.jvm.ClassReader$1.complete(ClassReader.java:240) at com.sun.tools.javac.code.Symbol.complete(Symbol.java:574) at com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:1037) at com.sun.tools.javac.code.Symbol$ClassSymbol.members(Symbol.java:978) at com.sun.tools.javac.jvm.ClassReader$AnnotationDeproxy.visitEnumAttributeProxy(ClassReader.java:1854) at com.sun.tools.javac.jvm.ClassReader$EnumAttributeProxy.accept(ClassReader.java:1676) at com.sun.tools.javac.jvm.ClassReader$AnnotationDeproxy.deproxy(ClassReader.java:1814) at com.sun.tools.javac.jvm.ClassReader$AnnotationDefaultCompleter.run(ClassReader.java:1918) at com.sun.tools.javac.comp.Annotate.flush(Annotate.java:143) at com.sun.tools.javac.comp.Annotate.enterDone(Annotate.java:129) at com.sun.tools.javac.comp.Enter.complete(Enter.java:512) at com.sun.tools.javac.comp.Enter.main(Enter.java:471) at com.sun.tools.javac.main.JavaCompiler.enterTrees(JavaCompiler.java:982) at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.enterTrees(JavacProcessingEnvironment.java:1015) at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.<init>(JavacProcessingEnvironment.java:902) at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.next(JavacProcessingEnvironment.java:921) at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1187) at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170) at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:856) at com.sun.tools.javac.main.Main.compile(Main.java:523) at com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:129) at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:138) at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:239) at org.jetbrains.jps.javac.ExternalJavacProcess.compile(ExternalJavacProcess.java:189) at org.jetbrains.jps.javac.ExternalJavacProcess.access$400(ExternalJavacProcess.java:28)
最新发布
05-20
### 堆内存不足导致的 `CompletionException` 问题解决方案 #### 背景分析 `java.util.concurrent.CompletionException` 是一种包装异常,它通常用于异步操作中捕获并传递底层异常。在这种情况下,实际引发的问题是由 `java.lang.OutOfMemoryError: Java heap space` 导致的堆内存不足。 堆内存不足通常是由于 JVM 配置不当或者应用程序创建了过多的对象而未及时释放所致[^2]。以下是针对该问题的具体解决方法: --- #### 修改 JVM 参数以增加堆内存分配 通过调整 JVM 的参数可以有效缓解此问题。具体来说,可以通过设置 `-Xms` 和 `-Xmx` 来分别指定初始堆小和最小。例如,在 Tomcat 或其他 Java 应用程序中运行时,可以在启动脚本中加入如下配置: ```bash JAVA_OPTS="-Xms512m -Xmx2048m" export JAVA_OPTS ``` 上述命令将初始堆小设为 512MB,并允许其扩展到最多 2GB。如果是在 Windows 平台上,则可以直接编辑相应的 `.bat` 文件来完成相同的操作[^3]。 对于 IDEA 开发环境下的项目构建过程遇到此类错误的情况,也可以按照类似的逻辑去修改 VM options 设置位置之一即 File -> Settings -> Build, Execution, Deployment -> Compiler 中找到 “Build process heap size (Mbytes)” 字段将其数值增至适当范围比如 700 到 1024 MB之间即可满足多数需求。 另外如果是 Eclipse 用户的话则需前往 Window->Preferences->Java->Installed JREs 找到当前使用的 JDK 版本双击打开 Arguments Tab 输入同样的参数进去保存生效. 最后假如是在 Linux 上部署的服务进程发生 OutOfMemory 错误可通过查找对应服务进程号再利用 jmap 工具导出 dump 文件进一步定位哪些部分占用了量资源从而采取针对性措施优化代码减少不必要的对象生成频率以及加快垃圾回收机制触发时机等方式综合处理这个问题[^4]: ```bash ps aux | grep java # 查找目标进程中包含关键字 'java' 的行获取 PID 号码 jmap -dump:live,format=b,file=heap_dump.hprof <PID> # 替换<PID>为你刚才查出来的那个具体的数字值代表的目标服务进程编号 ``` 之后可借助专业的工具像 MAT(Memory Analyzer Tool), VisualVM 或者 JProfiler 对这些 hprof 格式的文件做深入剖析找出潜在瓶颈所在之处进而改进之. --- ### 总结 通过对 JVM 启动选项进行合理配置能够很好地预防由堆空间耗尽所引起的各种 CompletionException 类型的异常情况的发生。同时结合使用性能监控与诊断工具有助于更精准快速地发现深层次原因以便做出相应调整达到最佳效果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值