关于java.lang.OutOfMemoryError知多少(一)

概况

在实际的java应用中,我们可能会碰到成千上万中java.lang.OutOfMemoryError的,不过总体症状体现往往在如下8种情况中

一、java.lang.OutOfMemoryError之java heap space

1、简述

一般呢,java应用程序只能使用有限的内存,常常是在应用启动的时候来指定的比如参数-Xmx和-XX:MaxPermSize。内存会被划分为两个区域:heap(堆空间)和 permgen(永久代)


内存构成.png

对应区域的大小是在Java虚拟机(JVM)启动期间设置的,可以通过指定JVM参数- xmx和- xx:MaxPermSize来定制。若没有显式地设置大小,则使用特定于平台的默认值。
往往出现java.lang.OutOfMemoryError,是由于应用程序试图向堆空间区域添加更多数据时,却没有足够的空间,便会触发Java堆空间错误。

注意:即使当前机器可能有很多物理内存可用,JVM达到堆大小限制时,仍然会出现java.lang.OutOfMemoryError,抛出Java堆空间错误。

2、原因

对应上面出现的问题,是什么原因所致?接下来我们来剖析下:
比较常见的当我们常见将需要XXL大小的应用放入到S大小的java堆空间,中,由于应用所需空间不足,应用不能运行,导致OutOfMemoryError 。
除此之外其他程序错误的导致OutOfMemoryError的原因比较复杂:

  • 流量/数据峰值
    应用程序自身的处理存在一定的限额,比如一定数量的用户或一定数量的数据。而当用户数量或数据量突然激增并超过预期的阈值时,那么就会峰值停止前正常运行的操作将停止并触发java . lang.OutOfMemoryError:Java堆空间错误
  • 内存泄漏
    编程中存在的缺陷错误会导致应用程序不断消耗更多内存。每次使用该应用程序,都会将一些对象留到Java堆空间中。随着时间的推移,泄漏的对象消耗掉所有可用的Java堆空间,并触发已经熟悉的Java . lang.OutOfMemoryError:Java堆空间错误
3、实例

1、简单实例
分配2M大小int[],当我们通过java -Xmx12m OOM则会出现Java . lang.OutOfMemoryError; 而一旦将java堆空间设置为13M则不会出现OutOfMemoryError

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

关于数组内存大小计算:
首先知道java对象内存构成 = 对象头(Header) + 实例数据(Instance Data)+ 对齐填充(Padding)
-对象头在32位系统上占用8bytes,64位系统上占用16bytes
-引用类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes;

-HotSpot的对齐方式为8字节对齐
-开启(-XX:+UseCompressedOops)会导致指针压缩,导致大小不一样,详情请自行学习《深入理解Java虚拟机》
上例:计算一维数组总内存 = 对象头(24bytes) + 4bytes * 210241024

>javac OOM.java
>java -Xmx12m OOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at OOM.main(OOM.java:4)
>java -Xmx13m OOM

2、内存泄漏
在Java中,开发人员创建并使用新对象(例如new Integer(5))时,不必自己分配内存——这是由Java虚拟机(JVM)负责的。在应用程序的生命周期中,JVM会定期检查内存中哪些对象仍在使用,哪些不是。未使用的对象可以被丢弃,内存被回收再利用。这个过程称为垃圾收集。负责收集的JVM中对应的模块称为垃圾收集器(GC)。
Java的自动内存管理依赖于GC周期性地查找未使用的对象并删除它们。简单地说,Java中的内存泄漏是一些对象不再被应用程序使用但垃圾收集无法识别的情况。因此,这些未使用的对象仍然在Java堆空间中无限期地存在。不停的堆积最终会触发java . lang.OutOfMemoryError

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);
   }
}

当执行上面的代码时,可能会期望它永远运行,不会出现任何问题,假设单纯的缓存解决方案只将底层映射扩展到10,000个元素,而不是所有键都已经在HashMap中。然而事实上元素将继续被添加,因为key类并没有重写它的hashCode()和equals()。
随着时间的推移,随着不断使用的泄漏代码,“缓存”的结果最终会消耗大量Java堆空间。当泄漏内存填充堆区域中的所有可用内存时,垃圾收集无法清理它,java . lang.OutOfMemoryError。
相对来说对应的解决方案比较简单:重写equals方法

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

二、java.lang.OutOfMemoryError之GC overhead limit exceeded

1、简述

Java运行时环境包含一个内置的垃圾收集(GC)过程。在许多其他编程语言中,开发人员需要手动地分配和释放内存区域,以便可以重用释放的内存。
另外,Java应用程序只需要分配内存。每当内存中的某个特定空间不再使用时,一个称为“垃圾收集”的单独进程就会为它们清除内存。
当应用程序耗尽所有可用内存时,GC开销限制超过了错误,而GC多次未能清除它,这时便会引发java.lang.OutOfMemoryError

2、原因

当JVM花费大量的时间执行GC,而收效甚微,而一旦整个GC的过程超过限制便会触发错误。默认的jvm配置GC的时间超过98%,回收堆内存低于2%。


实例图.png

若是JVM没有设置上图的限制,会带来什么样的后果:意味着GC能够清理的少量堆将很快再次被填充,迫使GC再次重新启动清洗过程。这形成了一个恶性循环,CPU 100%忙于GC,没有实际工作可做。应用程序的最终用户面临极度的减速——通常以毫秒为单位的操作需要几分钟才能完成。
因此," java. lang.OutOfMemoryError:GC开销上限“是一个很好的快速失败的例子

3、实例
class Wrapper {
  public static void main(String args[]) throws Exception {
    Map map = System.getProperties();
    Random r = new Random();
    while (true) {
      map.put(r.nextInt(), "value");
    }
  }
}

当我们执行 java -Xmx100m -XX:+UseParallelGC Wrapper
由于上述代码将不会被停止,很快我们就会得到java.lang.OutOfMemoryError:GC开销超过限额;不过该实例在不同堆大小或不同GC算法会得到不同的结果:
(1)、应用程序将抛出常见的java . lang.OutOfMemoryError及Java堆空间消息。
(2)、采用其他垃圾收集算法(比如- xx:+ UseConcMarkSweepGC或- xx:+ UseG1GC)运行它时,错误被默认的异常处理程序捕获,并且没有stacktrace,由于堆已经耗尽,stacktrace甚至不能填充异常中
在资源受限的情况下,无法预测应用程序将如何挂掉,因此不能将期望基于特定的操作序列来完成

三、解决方案

1、在某些情况下,可能由于分配给JVM的堆的数量不足以满足运行在JVM上的应用程序的需求。在这种情况下,一般是分配更多的堆来尝试解决下。指定JVM参数-Xmx的值
java -Xmx1073741824 XXX_Class
java -Xmx1048576k XXX_Class
java -Xmx1024m XXX_Class
java -Xmx1g XXX_Class
2、在许多情况下,提供更多的Java堆空间并不能解决问题。例如应用程序包含内存泄漏,添加更多堆将延迟java . lang.OutOfMemoryError:Java堆空间错误。此外,增加Java堆空间的数量也增加了影响应用程序吞吐量或延迟的GC暂停时间。
如果希望解决Java堆空间的底层问题,而不是掩盖这些症状,那么需要找出代码中哪个部分负责分配最多的内存.需要搞懂两点
(1)、哪个对象占据了大部分堆内存
(2)、在代码什么地方分配了这些对象
操作的过程大致如下:

  • 首先获得安全性许可,以便获取JVM执行heap dump。“dumps”基本上是堆内容的快照便于分析。这些快照可以包含机密信息,如密码、信用识别码等,因此,由于安全原因,导致不能获取这些dumps。
  • 其次要在合适的时刻获取dump;一旦获取dump的时刻不合适会有很多干扰信息存在dump文件里面不便于我们的分析。另外一个事实是获取dump时会“暂停”JVM,影响应用性能
  • 另外找一台机器用来分析这些dumps,并且结合当前这些jvm故障点使用heap大小,分析机器需要提供超过对应的heap大小heap来完成分析。这时我们可以使用专门的工具比如 [Eclipse MAT]、【JProfiler】甚至其他的专业软件。
  • 检测堆的最大占用者的GC根路径,这块对初学者存在一定的难度,不过我们尝试下
  • 最后结合前面分析的结果,弄清楚在您的源代码中,潜在危险的大量对象被分配到哪里【需要对自身的代码比较熟悉,这个过程相对比较简单的】
    如下是采用[Plumbr]分析个结果


    Plumbr分析.png

    分析结果如下:
    (1)、哪些对象正在消耗最多的内存(271个 com.example.map.impl.PartitionContainer实例消耗248MB总堆内存中的173MB。
    (2)这些对象在什么地方分配的(其中大部分[99%]分配在MetricManagerImpl类,第304行)
    (3)、当前引用这些对象的内容(完整的引用链到GC根)
    有了这些信息,就可以将定位底层的根源,并确保数据结构被缩减到合适内存使用级别。
    如果当从内存分析或阅读Plumbr报告得出的结论是,内存使用是合法的,并且在源代码中没有任何更改,这时就需要允许提供更多地运行Java堆空间给JVM。在这种情况下,就调整JVM启动配置,并添加(或增加当前的值), 类似-Xmx1024m

四、其他

最后给各位推荐一个阿里JVM大神寒泉子的公众号(lovestblog)及其开发的JVM参数小程序(JVMPcoket)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,286评论 17 311
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,488评论 3 83
  • 故黄河桥上交警拦住一辆电三轮,伸手拔掉车钥匙,要把三轮车拖走。三轮车的女主人,忙下跪磕头求交警高抬贵手。马上围了路...
    铭玥咏全阅读 173评论 0 0
  • 聚集函数 聚集函数(aggregate function)对某些行运行的函数,计算并返回一个值。 1AVG()函数...
    ATHAS阅读 769评论 0 50