前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OutOfMemoryError系列: Java heap space错误深度解析

OutOfMemoryError系列: Java heap space错误深度解析

作者头像
ImportSource
发布2018-04-03 14:28:32
6.6K0
发布2018-04-03 14:28:32
举报
文章被收录于专栏:ImportSourceImportSourceImportSource

本文包括JVM内存管理、错误产生的原因、内存泄漏的代码示例,最后还会介绍怎么解决这些问题,特别会提到一些性能诊断工具,让你快速的知道问题发生的根本原因。

java.lang.OutOfMemoryError:Java heap space

java的应用程序只被允许使用限定好的memory。在java的application启动的时候,这个内存大小就被规定好了。为了让内存管理更加的智慧,java的memory被分为了两个不同的区域。这两个区域分别被叫做heap space和Permanent Generation(很多书中叫“永久代”)。

这两个区域的大小在JVM启动的时候就被设置好了,是通过两个JVM参数-Xmx and -XX:MaxPermSize来设置的,如果你不专门去设置这两个的大小,那么将会使用平台定义好的默认值。

java.lang.OutOfMemoryError:java heap space 这个错误是在什么情况下发生呢?就是当你的application想要往heap那个空间里添加更多的数据的时候,但heap里却没有足够的空置区域的时候就会发生java heap space错误。

请注意,也许还有足够的物理内存,但是当JVM达到heap的大小限制时,就会抛出java.lang.OutOfMemoryError: Java heap space 错误。

怎么引起的?

java.lang.OutOfMemoryError这个错误的产生最常见的原因其实很简单:就是你的应用程序需要更多的 heap 空间。引起这个错误的其他原因就比较复杂了,可能是因为你的编程错误引起的,比如下面两种情况:

  • 使用/数据量的峰值。该应用程序旨在处理一定数量的用户或一定量的数据。当用户数量或数据量突然突增并超过预期阈值时,在尖峰停止操作之前正常运行的操作会触发java.lang.OutOfMemoryError:Java heap space 错误。
  • 内存泄漏(Memory leaks)。特定类型的编程错误将导致您的应用程序不断消耗更多的内存。每次应用程序的那个带有泄漏问题的函数被调用的时候,它就会将一些对象留在Java heap space中。随着时间的推移,那些被泄漏的对象会消耗掉所有可用的Java heap space,并触发这个你已很熟悉的java.lang.OutOfMemoryError:Java heap space 错误。

上代码 简单的例子:

第一个例子很简单 - 下面的Java代码试图分配一个2M整数的数组。 当你编译它并使用12MB的Java堆空间(java -Xmx12m OOM)启动时,它会失败,并返回java.lang.OutOfMemoryError:Java heap space 消息。 使用13MB的Java堆空间,程序就运行正常了。

class OOM {
  static final int SIZE=2*1024*1024;
  public static void main(String[] a) {
    int[] i = new int[SIZE];
   }
}

内存泄露的例子:

第二个更现实的例子是内存泄漏。在Java中,当开发人员创建和使用新对象new Integer(5),他们不必自己分配内存 - 这是由Java虚拟机(JVM)来处理。在应用程序的生命周期中,JVM会定期检查内存中的哪些对象仍在使用,哪些不是。未使用的对象可以被丢弃,内存被回收并再次使用。这个过程称为垃圾收集。 JVM中处理集合的相应模块称为垃圾回收器(GC)。

Java的自动内存管理依赖于GC定期查找未使用的对象并删除它们。简化一点我们可以说,Java中的内存泄漏是一种情况,其中一些对象不再由应用程序使用,但垃圾收集无法识别它。因此,这些未使用的对象将无限期地保留在Java堆空间中。这个堆积将最终触发java.lang.OutOfMemoryError:Java heap space 错误。

构造一个满足内存泄漏定义的Java程序是相当容易的,像下面这样:

class KeylessEntry {
 
   static class Key {
      Integer id;
 
      Key(Integer id) {
         this.id = id;
      }
 
      @Override
      public int hashCode() {
         return id.hashCode();
      }
   }
 
   public static void main(String[] args) {
      Map m = new HashMap();
      while (true)
         for (int i = 0; i < 10000; i++)
            if (!m.containsKey(new Key(i)))
               m.put(new Key(i), "Number:" + i);
   }
}

当你执行上面的代码,你也许希望它一直没问题的运行下去,并且认为这个缓存方案仅仅会增加到10000个key。然而事实是 key会一直被增加,因为Key这个class里边并没有包含equals()的实现,只是override了hashCode()方法。

最后,随着时间的推移,随着泄漏代码的不断使用,“缓存”结果最终消耗了大量的Java堆空间。 当泄漏的内存填满堆区域中的所有可用内存,并且Garbage Collection无法清除它时,会抛出java.lang.OutOfMemoryError:Java heap space 。

要想内存不再泄漏,其实办法很简单-就是加上 equals()方法的实现,像下面这样:

@Override
public boolean equals(Object o) {
   boolean response = false;
   if (o instanceof Key) {
      response = (((Key)o).id).equals(this.id);
   }
   return response;
}

怎么解决呢?

在某些情况下,你分配给JVM的heap数量不足以满足在该JVM上运行的应用程序的需求。 在这种情况下,你只需要分配更多的heap就可以了 - 请参见本章末尾如何实现。

然而,在许多情况下,提供更多的Java堆空间不会解决问题。 例如,如果您的应用程序包含内存泄漏,添加更多堆将只是推迟java.lang.OutOfMemoryError:Java heap space 错误。 此外,增加Java堆空间量也会增加影响应用程序吞吐量或延迟的GC暂停时间。

如果你想解决Java堆空间的根本问题,而不是掩盖症状,你需要找出那些分配了最多的内存的那些代码。 换句话说,你需要回答这些问题:

1.哪些对象占用了heap的大部分?

2.这些对象分布在源码中的哪个地方?

此时,你要花上一些时间来解决这些问题了。这里是一个粗略的过程大纲,将帮助您回答上述问题:

获取安全许可,以便从JVM中把heap中的内容dump出来存储到另外一个地方。 “Dumps”基本上就是堆内容的一个快照,你稍后就是在这些dumps中进行分析。由于这些快照可能包含机密信息,例如密码,银行卡号等,所以你必须要获得安全部门的允许。

选择在一个合适的时间进行dump(转储)操作。如果时机不对,堆垃圾可能包含大量的噪音,甚至可能几乎没有什么有用的信息。另一方面,每个堆的dump(转储)会完全“冻结”JVM,这样会占用过多的JVM,这种情况下很可能会影响正常业务的访问,会出现一些性能问题。

专门找一台机器用作dump(转储)。当你要分析8GB的堆,那你就要一台超过8GB的机器来分析堆内容。然后选择一个转储分析软件(我们建议使用Eclipse MAT,你也可以选择其他转储分析软件)。

检测出堆的最大消费者的GC根的路径。我们在这里单独的一篇文章中介绍了这项活动。

接下来,你需要确定源代码中的哪些位置正在分配潜在危险的大量对象。如果你对应用程序的源代码很了解的话,你将能够在几次搜索中做到这一点。

或者,我们建议你使用Plumbr,这是一个jvm性能检测工具,它可以自动检测出问题的根本原因。 在其他性能问题上,它同样可以捕获所有java.lang.OutOfMemoryErrors,并且可以自动的为你整理出那些非常“内存饥饿”(memory-hungry)的数据结构的信息。

Plumbr会在幕后为你收集必要的数据 - 包括有关堆使用的相关数据(只有对象布局图,没有实际数据),以及一些在“堆转储”(heap dump)中找不到的数据。 如果JVM遇到java.lang.OutOfMemoryError,Plumbr还会为您执行必要的数据处理 。 下面是一个Plumbr的有关java.lang.OutOfMemoryError错误的一个例子(分析结果):

没有任何额外的工具或分析,你就可以看到:

  • 哪些对象消耗的内存最多(271个com.example.map.impl.PartitionContainer实例在248MB的总堆中消耗173MB)
  • 这些对象是在哪个类中被分配的?(大多数在MetricManagerImpl类中分配,行304)
  • 当前引用这些对象的完整引用链(GC引用的完整引用链)

有了这些信息,您就可以找到问题的根本原因了,然后把数据结构修剪到它们适合在您的内存池中的级别。

但是,当您从内存分析或阅读Plumbr报告得出的结论是内存使用是合法的,那么就没必要修改源代码了,这时候你就要设置更大的java heap 空间来保证应用程序的运行了。 请更改JVM启动配置,并添加(或增加值,如果存在)以下内容:

-Xmx1024m

上面的配置将给应用程序1024MB的 Java heap space。 您可以使用g或G表示GB,m或M表示MB,k或K表示KB。 例如,下面的几种方式都表示最大Java heap space为1GB:

    java -Xmx1073741824 com.mycompany.MyClass
    java -Xmx1048576k com.mycompany.MyClass
    java -Xmx1024m com.mycompany.MyClass
    java -Xmx1g com.mycompany.MyClass
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ImportSource 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java.lang.OutOfMemoryError:Java heap space
    • 怎么引起的?
    相关产品与服务
    检测工具
    域名服务检测工具(Detection Tools)提供了全面的智能化域名诊断,包括Whois、DNS生效等特性检测,同时提供SSL证书相关特性检测,保障您的域名和网站健康。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档