JVM调优实战

收到线上环境JAVA内存异常邮件,登录线上服务器查看异常日志:

以下是分析OOM的原因与定位内存泄漏步骤:

  • 下载堆dump文件(文件比较大,需要一段时间)
  • 利用jstack -l <pid>(进程id) > text.txt 将所有线程运行状态导入文件中,下载之后以便分析。
  • 利用jdk自带的jvisualvm工具打开dump文件,这里也可以用MAT或者是JProfiler。

通过jvisualvm打开的堆转储快照文件可以看出,在某个线程执行时发生了OOM。

查看一下各个类的实例数和占用内存大小:

可以看到ThreadLocal有31万多个实例,差不多占用10M内存,怀疑内存泄漏和线程有关。

打开线程快照:

 

从线程快照来看,线程池创建了3000多个,结合堆dump文件可以推断出线程池没有及时关闭,导致新的线程申请不到内存空间,从而导致OOM。

结合GC日志来看一下:

从GC日志可以看出,此时Full GC相当频繁,并且Full GC之后,还是申请不到足够的内存。

通过以上堆dump文件、线程快照以及GC日志,综合来分析,导致内存泄漏的原因是线程池没有及时关闭,从而占用过多的内存,导致新创建的线程申请不到足够内存而导致内存溢出。通过回代码中查找,发现代码中存在大量局部创建线程池的定时任务。

附:JVM参数(服务器为4核8G)

-server \  #以服务器模式运行
-Xms2048m -Xmx2048m  \  #最小堆内存与最大堆内存相同,避免频繁Minor GC,频繁申请内存。一般设置为服务器物理内存的1/4左右
-XX:PermSize=512M \   #老年代大小
-XX:MaxPermSize=512m \ #最大老年代大小
-Xss4m \ #设置每个线程的堆栈大小,在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右
-Xmn768m \  #年轻代大小,一般为堆内存的3/8
-XX:+AggressiveOpts \ #启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等
-XX:+UseBiasedLocking \#启用偏向锁
-XX:+CMSParallelRemarkEnabled \#降低标记停顿	
-XX:+UseConcMarkSweepGC \ #启用CMS低停顿垃圾收集器,减少FGC的暂停时间
-XX:ParallelGCThreads=2 \
-XX:SurvivorRatio=2 \ #Survivor:Eden = 2:2(Survivor永远都是2)
-verbose:gc \#可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。
-XX:+PrintGCDetails \ #打印详细GC日志
-XX:+PrintGCDateStamps \ #输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC \ #每一次GC后,都打印堆信息
-XX:+HeapDumpOnOutOfMemoryError \#发生OOM时候,生成堆快照文件
-XX:HeapDumpPath=/usr/local/tomcat/logs \ #指定堆dump文件存放目录
-Xloggc:./gc.log #指定GC日志目录
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9007 \ #开启远程调试参数

更多JVM参数设置,请参考:http://blog.sina.com.cn/s/blog_55d3f3300102w42u.html

总结:

  1. 局部创建的线程池、连接池等在使用完之后需要及时关闭,避免造成内存泄漏。
  2. 寻找内存泄漏,应该结合堆转储dump文件和线程dump综合来查找问题。

知识扩展:

  • JVM调优命令总结:

     a.jmap -heap <pid>,可以看到JVM内存使用情况。

     

    b.jmap -histo[:live] <pid>,打印java堆中各个对象的数量大小,加上live只打印活跃的对象。

    c.jstack -l <pid>,生成java虚拟机当前时刻的线程快照。

    d.jmap -dump[:live],format=b,file=heap-dump.hprof <pid> 生成堆快照文件。

    e.jstat -gcutil <pid> 1000 10 1秒钟打印10次gc统计信息,达到以下标准则不需要优化。

  1. Minor GC执行时间不到50ms;
  2. Minor GC执行不频繁,约10秒一次。
  3. Full GC执行不到1s。
  4. Full GC执行频率不算频繁,不低于10分钟一次。     
  • GC日志可视化分析工具gcviewer

从图中可以清楚的看出年轻代、老年代占用的内存大小以及走势。还可以知道Minor GC、Full GC次数和花费的时间。

  • OOM异常是否会导致JVM退出?----(美团面试题) 
  1. OOM说到底只是异常的一种类型,一个线程的OOM,会导致该线程退出,然后释放该线程占用的内存。
  2. 如果该线程释放的内存足够大,能够满足其他线程申请内存需要,则JVM依然能够继续运行。否则,就会导致其他线程也申请不到内存空间发生OOM,引起连锁反应,导致整个JVM退出。(只有所有非守护线程都退出的时候,JVM才会退出

 

以上是个人的一点愚见,如果有不对或者错误,请大神指出。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页