JVM OOM分析与调优

OutOfMemoryError
除了程序计数器外,其他的几个运行数据区都有可能发生OutOfMemoryError(OOM)的可能。
所以在遇到OOM的问题时应能根据异常的信息快速定位到时哪一个内存区域的内存溢出,知道什么样的代码会致使OOM,以及该如何处理。 
 
一、Java堆溢出
  Heap堆是OOM故障最主要的发源地,它存储着几乎全部的实例对象。在线上生产环境中,JVM的Xms和Xmx应设置成同样的大小 ,避免在GC后调整堆大小时带来额外的压力。
  模拟:不停的建立对象,而且GC Roots到对象之间有可达路径,超过堆的最大内存容量就会OOM。 如,在while(true)循环中不停的建立对象并将对象add到List集合中
 
  Java堆内存溢出时异常堆栈信息会在“java.lang.OutOfMemoryError”后提示Java heap space
 
  如何定位?
    1)设置参数-XX:+HeapDumpOnOutOfMemoryError 来配置当内存耗尽时记录下当时的内存快照,通常根据MAT分析具体缘由是内存泄漏仍是内存溢出
    2)内存泄漏根据工具查看泄漏对象到GC Roots的引用链,找到泄漏对象时经过怎么样的路径与GC Roots相关联并致使垃圾收集器没法自动回收它们的。根据对象类型和GC Roots引用链就能够准肯定位到泄漏代码的位置。
    3)内存溢出检查虚拟机参数堆参数可否扩大,代码上检查是否存在某些对象生命周期过长、持有状态时间过长。
 
二、虚拟机栈和本地方法栈溢出
  HotSpot虚拟机不区分虚拟机栈和本地方法栈,所以设置本地方法栈的参数-Xoss其实是无效的,栈的参数只能根据-Xss设定
  若是线程请求的栈深度超过了JVM容许的最大深度,将抛出StackOverflowError   
  若是虚拟机在扩展栈时没法申请到足够的内存将跑出OutOfMemoryError
  1)StackOverflowError   容易定位,多是递归没有出口,正常的方法调用深度在1000-2000之间彻底没有问题
  2)OOM多是创建的线程数量很是多,每一个线程瓜分栈内存,当线程都存活着没有足够的内存去分配给线程时会抛出OOM
 
三、方法区和运行时常量池溢出
  Java方法区和运行时常量池溢出异常堆栈信息会在“java.lang.OutOfMemoryError”后提示PermGen space
  如String字符串存放在常量池中
 
四、本机直接内存溢出
  Unsafe的allocateMemory能够申请分配内存(Unsafe实例可经过反射获取)
  由DirectMemory致使的内存溢出在Heap Dump文件中看不出明显的异常,若是OOM以后的Dump文件很小,而程序中直接或者间接使用了NIO,那多是这方面的缘由。
  DirectMemory-XX:MaxDirectMemory指定,若是不指定默认与Java堆内存最大值同样。
  垃圾收集时,虚拟机 只有在Full GC时会顺便回收DirectMemory中废弃的对象。所以DirectMemory内存满了以后,只有等待系统的下一次Full GC,或者抛出内存异常在catch中调用System.gc()
 
 
内存溢出排查缘由

  一、GC日志分析html

  为了在内存溢出时排查缘由,能够在JVM启动时加一些参数来控制,当JVM内存出问题时能够经过分析记录下来的GC日志,GC的频率和每次GC回收了哪些内存。java

 GC的日志输入有如下参数linux

  1)-verbose:gc  能够辅助输出一些详细的GC信息sql

  2)-XX:+PrintGCDetails  输出GC的详细信息数组

  3)-XX:+PrintGCApplicationStoppedTime  输出GC形成应用程序暂停的时间浏览器

  4)-XX:+PrintGCDateStamps  输出GC发生的时间信息服务器

  5)-XX:PrintHeapAtGC  在GC先后输出堆中各个区域的大小eclipse

  6)-Xloggc:[file]  将GC信息输出到单独的文件工具

 

每种GC方式输出日志的形式不一样,除CMS的日志和其余GC方式差别较大外,其他GC方式的日志能够抽象成以下方式性能

  [GC [<collector>: <starting occupancy1> -> <ending occupancy1> (total size1) , <pause time1> secs]

  <starting occupancy2> -> <ending occupancy2> (total size2) , <pause time2> secs] ]

说明以下

  1)<collectot>GC  表示垃圾收集器的名称

  2)<starting occupancy1>  表示Young区在GC前占用的内存

  3)<ending occupancy1>  表示Young区在GC后占用的内存

  4)(total size1)  表示Young区的总内存大小

  5)<pause time1>   表示Young区局部收集时JVM暂停处理的时间 secs表示单位秒

  6)<starting occupancy2>   表示Heap在GC前占用的内存

  7)<ending occupancy2>  表示Heap在GC后占用的内存

  8)(total size2)  表示Heap的总内存

  9)<pause time2>  表示在GC过程当中JVM暂停处理的总时间

 

能够根据日志来判断是否存在内存泄漏的问题:

  <starting occupancy1> - <ending occupancy1>  和 <starting occupancy2> - <ending occupancy2> 比较

  一、若是前者差等于后者差,代表Young区GC 对象100%被回收,没有对象进入 Old区或者Perm区

  二、若是前者大于后者,那么差值就是此次GC对象进入Old或者Perm区的大小

  若是随着时间的的延长,<ending occupancy2>的大小一直在增加,并且Full GC很频繁,那么极可能就是内存泄漏致使的。

 

 

  二、堆快照文件分析

   如下Java命令在JDK的bin目录下执行

    经过命令   jmap -dump:format=b,file=[filename][pid]  jmap(Memory Map for Java)

来记录下堆的内存快照,而后利用第三方工具如eclipse 插件MAT来分析整个Heap的对象关联状况。

  若是内存耗尽可直接致使JVM退出,能够经过参数

  -XX:+HeapDumpOnOutOfMemoryError 来配置当内存耗尽时记录下当时的内存快照

  -XX:HeapDumpPath  指定内存快照文件的路径  文件快照的名称格式为 java_[pid].hprof

 

  若是是OOM,可能有两方面的缘由

  一、内存分配太小,不知足程序运行所须要的内存

  二、内存泄漏(FullGC频繁,回收后Heap占用的内存不断增加)

 

  三、JVM Crash 日志分析

  TODO

 

JVM性能监控和故障处理 

  经过工具导出和处理分析 运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(headdump/hprof文件)等

  jps:JVM process status tool,显示指定系统内全部的HotSpot虚拟机进程

  jstat:JVM statistics Monitoring Tool,收集HotSpot虚拟机各方面的运行数据

  jinfo:Configuration Info for java,显示虚拟机配置信息

  jmap:Memory map for Java,生成虚拟机的内存转储快照(heapdump文件)

  jhat:JVM Heap Dump Browser,用于分析heapdump文件,它会创建一个http/html服务器,让用户能够在浏览器上查看分析结果

  jstack:Stack trace for Java,显示虚拟机的线程快照

 

  jps:(Jvm Process Status )和linux中ps命令类似,可列出正在运行的虚拟机进程,并显示虚拟机执行主类和这些进程的惟一ID(Local Virtual Machine Identifier LVMID)

  格式:jps [option] [hostid(主机名)]

  参数:

    -l  输出主类的全路径  

    -v  输出虚拟机进程启动是的JVM参数

   

  jstat:用于监视虚拟机各类运行状态信息的命令行工具,它能够显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据,是定位虚拟机性能问题的首选工具。

  格式:jstat [ option vmid [interval] [count] ]

  interval和count是查询间隔和次数,若是忽略这两个参数则只查询一次

  选项:

    -class 监视类装载、卸载数量、总空间以及类装载消耗时间

    -gc 监视Java堆情况,包含Eden区,两个Survivor区、老年代、永久代等的容量,已用空间,GC时间等信息。

 

  jmap:Java内存映射工具,用于生成堆存储快照heapdump,生成堆快照还能够经过设置参数使在OOM异常以后自动生成堆dump文件。

  jmap还可查询finalize执行队列、Java堆和永久代的详细信息(空间使用率,使用哪一种收集器等)。

  格式:jmap [option] vmid

  option参数

    -dump:format=b,file=[filename] 生成堆转储快照

    -heap  显示Java堆详细信息,如使用哪一种收集器、参数配置、分代情况等。

 

  jhat:与jmap搭配使用,分析jmap生成的堆转储快照文件。通常不会直接使用jhat命令分析dump文件,一是不会直接在应用服务器上分析dump文件,由于分析耗时耗资源,二是jhat分析结果比较简陋,可用VisualVM,MAT等工具

 

  jstack:Java堆栈跟踪工具,用户生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成快照的主要目的是定位线程出现长时间等待的缘由,如线程间死锁、死循环、请求外部资源(如sql)致使的长时间等待等

  线程出现停顿的时候经过jstack来查看各个线程的调用堆栈,就能够知道没有响应的线程到底在后台作什么事情或者等待什么资源

  格式:jstack [option] vmid

  option参数:

    -F:强制输出线程堆栈

    -l:除堆栈外显示关于锁的附加信息

    -m:可显示调用本地方法的堆栈

  JDK1.5以后的Thread类新增了getAllStackTraces()方法用户获取虚拟机中全部线程的StackTraceElement对象。和jstack功能相似

 public static Map<Thread,StackTraceElement[]> getAllStackTraces()   返回从 ThreadStackTraceElement 数组的一个 Map,表明相应线程的堆栈跟踪。 、

 
总结: 
 
  若是要定位OOM问题使用jps和jmap组合命令,先用jps或者linux的ps命令查看虚拟机进程的vmid,而后用jamp命令生成堆快照文件,最后使用工具分析dump文件定位问题。

  若是要定位线程响应时间过长的问题,使用jps和jstack命令,先用jps或者linux的ps命令查看虚拟机进程的vmid,而后用jstack命令查看线程堆栈信息

 

 

JVM调优

JVM调优是经过分析GC日志等来分析java内存和垃圾回收的状况,来调整各内存区域内存占比和垃圾回收策略。

GC优化的根本缘由
  垃圾收集器的工做就是清除Java建立的对象,垃圾收集器须要清理的对象数量以及要执行的GC数量均取决于已建立的对象数量。所以,为了使你的系统在GC上表现良好,首先须要在保证功能的基础上减小建立对象的数量。
  如:养成以下的良好的编码习惯
    一、使用StringBuffer或StringBuilder来代替String
    二、尽可能少输出日志

GC优化的两个目的
  一、减小Full GC的频率
  二、减小Full GC的执行时间

一、减小Full GC的频率——将进入老年代的对象数量降到最低
  充分使用系统资源,减小GC停顿时间和停顿次数,因为Full GC的停顿时间远比Minor GC的停顿时间长,所以要控制Full GC的频率。
控制Full GC的频率的关键是将进入老年代的对象数量降到最低。所以要看应用中的绝大多数对象是否符合“朝生夕灭”的原则,即大多数的对象的生存时间都不该太长,尤为是不能有成批量的、长时间存活的对象产生,这样这些对象在Minor GC就会被回收,不会进入老年代,这样才能保证老年代的稳定。

 

好比对于十几小时乃至一天才出现一次Full GC的系统能够经过定时任务的方式在夜间触发Full GC。

 

若是FullGC次数过多多是下面的缘由:
  一、内存占用高:代码中建立了大量的对象致使内存泄漏,不能回收内存,建立新对象致使空间不足触发fullGC
  二、内存占用不高:多是显示的调用System.gc()次数太多致使的fullGC,能够经过添加-XX:+DisableExplicitGC来禁用JVM对显式GC的响应

 

二、减小Full GC的时间
  Full GC的停顿时间远比Minor GC的停顿时间长,所以,若是在Full GC上花费过多的时间(超过1s), 将可能出现超时错误。

    1)若是经过减少老年代内存来减小Full GC时间,可能会引发OutOfMemoryError或者致使Full GC的频率升高。

    2)若是经过增长老年代内存来下降Full GC的频率,Full GC的时间可能所以增长。

    所以,你须要把老年代的大小设置成一个“合适”的值。

    

    影响GC性能的因素:

      1)-XX:NewRatio 新生代和老年代的内存比(默认1:2),这个参数将对GC性能产生重要的影响

      2)垃圾收集器的类型 

    何时须要进行GC优化?  经过监控GC状态,而后分析GC监控结果来判断是否须要进行GC优化。  若是GC执行时间知足下列全部条件,就没有必要进行GC优化了:    1)Minor GC执行很是迅速(50ms之内)    2)Minor GC没有频繁执行(大约10s执行一次)    3)Full GC执行很是迅速(1s之内)    4)Full GC没有频繁执行(大约10min执行一次)    GC优化是一个不断尝试并逐渐调试的过程。在每次设置完GC参数后,要收集数据,并收集至少24个小时以后再进行结果分析。
相关文章
相关标签/搜索