给我1万字,也讲Java不清内存排查。1万不行来2万~.

本篇文章是《Java内存故障?只是因为你不够帅!》这篇文章的续篇 。 上篇侧重于理论 , 本篇侧重于实践 。 对于内存问题排查来说 , 搞理论的痛苦 , 搞实践的也痛苦 , 没有一片清净之地 。
why?因为内存溢出是Java码农永远的伤 。
溢出有很多种解释 , 有精满自溢 , 有缓冲区溢出攻击 , 还有另外一种叫做领导的溢出 。 不知道什么叫作溢出理论 , xjjdog在此普及一下 。
《领导看了会炸毛的溢出理论》
内存溢出什么最重要?其实 , 内存溢出就像是一场交通事故 。 事故的发生方 , 就是具体的服务;事故的处理方 , 就是相关的程序员 。 其中有一个最重要的环节 , 就是在事故现场需要拍照取证 。
如果没有照片没有行车记录仪没有证据 , 就只能靠那张嘴 , 怎么说都是不可信的 。
**这句话很重要很重要:内存问题排查什么最重要?当然是信息收集 , 留下一些为我们的排查提供支持的依据 。 **千万不要舍本逐末 , 对内存问题排查本身感兴趣 , 那是自虐行为 。
有很多工具可以帮助我们定位问题 , 但前提是你得把它留下来 。 下面这篇文章是xjjdog很久之前留下来的 , 由于标题的缘故 , 你可能忽略了 , 但这些工具能够快速帮我们定位问题 。
《将java进程转移到“解剖台”之前 , 法医都干了什么?》
实际上 , 这个要求在我看来是强制的 。
【给我1万字,也讲Java不清内存排查。1万不行来2万~.】很多同学上来就说 , 我的内存溢出了 。 但你和它要一些日志信息 , 要堆栈 , 要现场保存的快照 。 都没有 。 这就是纯粹来搞笑的 。
下面是JDK8或者以下的GC日志参数 , 可以看到还是很长的 。
#!/bin/shLOG_DIR="/tmp/logs"JAVA_OPT_LOG="-verbose:gc"JAVA_OPT_LOG="${JAVA_OPT_LOG}-XX:+PrintGCDetails"JAVA_OPT_LOG="${JAVA_OPT_LOG}-XX:+PrintGCDateStamps"JAVA_OPT_LOG="${JAVA_OPT_LOG}-XX:+PrintGCApplicationStoppedTime"JAVA_OPT_LOG="${JAVA_OPT_LOG}-XX:+PrintTenuringDistribution"JAVA_OPT_LOG="${JAVA_OPT_LOG}-Xloggc:${LOG_DIR}/gc_%p.log"JAVA_OPT_OOM="-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=${LOG_DIR}-XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log"JAVA_OPT="${JAVA_OPT_LOG}${JAVA_OPT_OOM}"JAVA_OPT="${JAVA_OPT}-XX:-OmitStackTraceInFastThrow"下面是JDK9及其以上的日志配置 。 可以看到它的配置方式全变了 , 而且不向下兼容 。 Java搞的这个变化还是挺蛋疼的 。
#!/bin/shLOG_DIR="/tmp/logs"JAVA_OPT_LOG="-verbose:gc"JAVA_OPT_LOG="${JAVA_OPT_LOG}-Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=${LOG_DIR}/gc_%p.log:tags,uptime,time,level"JAVA_OPT_LOG="${JAVA_OPT_LOG}-Xlog:safepoint:file=${LOG_DIR}/safepoint_%p.log:tags,uptime,time,level"JAVA_OPT_OOM="-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=${LOG_DIR}-XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log"JAVA_OPT="${JAVA_OPT_LOG}${JAVA_OPT_OOM}"JAVA_OPT="${JAVA_OPT}-XX:-OmitStackTraceInFastThrow"echo$JAVA_OPT一旦发现了问题 , 就可以拿GC日志来快速定位堆内问题 。 但是并不是让你一行行去看 , 那太低效了 。 因为日志可能会很长很长 , 而且也不一定看得懂 。 这个时候 , 就可以使用一些在线工具辅助解决 。 我经常使用的是gceasy , 下面是它的一张截图 。
java-Xmx20m-Xmn4m-XX:+HeapDumpOnOutOfMemoryError-OOMTest[18.386s][info][gc]GC(10)ConcurrentMark5.435ms[18.395s][info][gc]GC(12)PauseFull(AllocationFailure)18M->18M(19M)10.572ms[18.400s][info][gc]GC(13)PauseFull(AllocationFailure)18M->18M(19M)5.348msExceptioninthread"main"java.lang.OutOfMemoryError:JavaheapspaceatOldOOM.main(OldOOM.java:20)jvisualvm的反应 。
java-Xmx20m-Xmn4m-XX:+HeapDumpOnOutOfMemoryError-XX:MetaspaceSize=16M-XX:MaxMetaspaceSize=16MMetaspaceOOMTest6.556s][info][gc]GC(30)ConcurrentCycle46.668msjava.lang.OutOfMemoryError:MetaspaceDumpingheapto/tmp/logs/java_pid36723.hprof..jvisualvm的反应 。
java-XX:MaxDirectMemorySize=10M-Xmx10MOffHeapOOMTestExceptioninthread"Thread-2"java.lang.OutOfMemoryError:Directbuffermemoryatjava.nio.Bits.reserveMemory(Bits.java:694)atjava.nio.DirectByteBuffer.(DirectByteBuffer.java:123)atjava.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)atOffHeapOOMTest.oom(OffHeapOOMTest.java:27)...栈溢出代码 。
java-Xss128KStackOverflowTestExceptioninthread"main"java.lang.StackOverflowErroratjava.io.PrintStream.write(PrintStream.java:526)atjava.io.PrintStream.print(PrintStream.java:597)atjava.io.PrintStream.println(PrintStream.java:736)atStackOverflowTest.a(StackOverflowTest.java:5)哪些代码容易出现问题忘记重写hashCode和equals看下面的代码 。 由于没有重写Key类的hashCode和equals方法 。 造成了放入HashMap的所有对象 , 都无法被取出来 。 它们和外界失联了 。
结果集失控 条件失控代码 。 与之类似的就是条件失控 , 当某个条件不满足的时候 , 将会造成结果集的失控 。 大家可以看下面的代码 , fullname和other为空的时候 , 会出现什么后果?
现象环境:CentOS7 , JDK1.8 , SpringBootG1垃圾回收器刚启动没什么问题 , 慢慢放量后 , 发生了OOM系统自动生成了heapdump文件临时解决方式:重启 , 但问题依然发现信息收集日志:GC的日志信息:内存突增突降 , 变动迅速堆栈:ThreadDump文件:大部分阻塞在某个方法上压测:使用wrk进行压测 , 发现20个用户并发 , 内存溢出wrk-t20-c20-d300sMAT分析堆栈文件获取:
jmap-dump:format=b,file=heap.bin37340jhsdbjmap--binaryheap--pid37340MAT工具是基于eclipse平台开发的 , 本身是一个Java程序 。 分析HeapDump文件:发现内存创建了大量的报表对象 。
解决分析结果:
系统存在大数据量查询服务 , 并在内存做合并当并发量达到一定程度 , 会有大量数据堆积到内存进行运算解决方式:
重构查询服务 , 减少查询的字段使用SQL查询代替内存拼接 , 避免对结果集的操作举例:查找两个列表的交集案例分析二现象环境:CentOS7 , JDK1.8 , JBossCMS垃圾回收器操作系统CPU资源耗尽访问任何接口 , 响应都非常的慢分析发现每次GC的效果都特别好 , 但是非常频繁了解到使用了堆内缓存 , 而且设置的容量比较大缓存填充的速度特别快!结论:
开了非常大的缓存 , GC之后迅速占满 , 造成GC频繁案例分析三现象java进程异常退出java进程直接消失没有留下dump文件GC日志正常监控发现死亡时 , 堆内内存占用很少 , 堆内仍有大量剩余空间分析XX:+HeapDumpOnOutOfMemoryError不起作用监控发现操作系统内存持续增加下面这些情况都会造成程序退出而没什么响应 。
被操作系统杀死dmesgoom-killerSystem.exit()javacom.cn.AA&后终端关闭kill-9解决发现:
在dmesg命令中发现确实被oom-kill解决:
给JVM少分配一些内存 , 腾出空间给其他进程案例分析四请参见堆外内存排查这篇文章 。 《Java堆外内存排查小结》
End


    推荐阅读