Java 性能优化实战记录(3)--JVM OOM的分析和原因追查

前言:
  C/C++的程序员渴望Java的自由, Java程序员期许C/C++的约束. 其实那里都是围城, 外面的人想进来, 里面的人想出去.

背景:
  作为Java程序员, 除了享受垃圾回收机制带来的便利外, 还深受OOM(Out Of Memory)的困惑和折磨. 本文借鉴了<<深入理解 Java虚拟机>>, 并结合了小编自身的经历和读者一起面对OOM的困局如何分析和破解.

准备工作:
  工欲善其事必先利其器, 对java进程的快照分析, 是能够帮助我们迅速的定位出错的原因. 这边我们着重介绍内存分析相关的点.
  借助MAT(Memory Analyzer Tool)来分析内存快照.
  MAT的下载地址:
  http://www.eclipse.org/mat/downloads.php
  eclipse的插件url:
  http://download.eclipse.org/mat/1.4/update-site/
  MAT的展示效果如图所示:

内存快照:
  如何去触发java进程生成内存快照呢?
  1). 设定jvm参数, 使得进程在特定条件下进程内存dump.

-XX:+HeapDumpOnOutOfMemoryError	

  评注: 设置如下虚拟机参数后, 当java进程因为内存溢出, 就会自动对内存进行Dump, 以便相关人员事后进行分析/解读.
  该方案非常具有针对性, 在崩溃点分析往往能立马定位问题的所在. 该参数设定方案相当于飞机的黑盒子, 记录了出事前所有信息.
  当然该方案, 最大的局限性就是不能随时随地进行分析, 那如何破呢?
  2). JMap工具来生成
  JMap的使用命令描述:

Usage:
  jmap [option] <pid>
    (to connect to running process)
  jmap [option] <executable <core>
    (to connect to a core file)
  jmap [option] [server_id@]<remote server IP or hostname>
    (to connect to remote debug server)

where <option> is one of:
  -dump:<dump-options> to dump java heap in hprof binary format
    dump-options:
    live	dump only live objects; if not specified,
          all objects in the heap are dumped.
    format=b binary format
    file=<file> dump heap to <file>
  Example: jmap -dump:live,format=b,file=heap.bin <pid>

  评注: 挑选了部分选项说明, 重要的是dump的规则
  使用命令如下所示:

jmap -dump:format=b,file=xxx.hprof <pid> 

OOM的原因分类:
  导致程序出现OOM的因素有多种. 大致我们可以把OOM简单的分为堆溢出(Heap), 栈溢出(Stack), 永久代溢出(常量池/方法区), 直接内存溢出.
  先来看下java的内存分布
  1). 堆溢出(heap)
  编写如下例程:

public static void main(String[] args) {
  List<byte[]> datas = new ArrayList<byte[]>();
  while ( true ) {
    datas.add(new byte[1024 * 1024]);
  }
}

  同时设置虚拟机参数, 使得java进程能够快速生成heapdump.

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

  评注: 设置heap的内存限制在20m, Xms/Xmx分别对应heap的初始和最大heap大小.
  产生的异常信息:

  mat进行内存分析

  评注: 从这边能看出, 这边聚集了众多的对象, 占据了99%的内存量.
  2). 栈溢出(stack)
  栈溢出的异常, 还是具有明确的指向性的. 暂略.
  3). 永久代溢出(常量池/方法区)
  <<深入理解 Java虚拟机>>阐述了String.intern()和gclib产生动态代理类的两个例子.
  其错误也很有指向性:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

  这边讲述下, 小编(mumuxinfei)很久之前做的一个项目.
  当时是机顶盒项目(采用IPanel浏览器, 相当于jvm虚拟机), 采用J2ME规范(java 1.1). 不过当时的机器性能不好, 内存少, 不像现在, 一些移动设备配置开始像PC靠齐了.
  我把一些初始化数据放在类里声明并定义时, 编译没问题, 但是jvm无论如何也run不起来, 总是出错. 不过我把这些数据放在文件里时, 通过运行时载入, 这样就没问题. 当时的我特别疑惑, 不都是内存吗? 同样的数据, 同样的内存, 这么差别就这么大呢?
  当时的我不理解, 后来知道java的内存结构, 我就明白了. 载入的数据放在Heap中, 而定义的常量是放在PermGen空间中. 而当初的设备限制, IPanel浏览器的默认永久空间大小比较小, 导致我只要把一些数据写到常量中定义, 就运行失败.
  不过蛮荒时代, 总是过去了, 现在的机器配置, 你是不会遇到这么诡异的事了. 日子在一天一天的变好, 过去苦难的日子, 现在的小年轻, 你是不会懂的...
  4). 直接内存溢出
  <<深入理解 Java虚拟机>>讲述了DriectByteBuffer的例子. 当然最直接, 也是最容易遇到的, 还是JNI的使用, 搞Android开发的大拿故意能遇到. 这边也略过不讲.

后记:
  从某种角度来说, <<深入理解 Java虚拟机>>已经把OOM的原因和例子说得非常具体了, 小编难以去超越它, 也不愿简单的复制罗列抄袭之. 简单的描述, 权当作学习笔记了.

原文地址:https://www.cnblogs.com/mumuxinfei/p/3899112.html