当前位置: 首页 > 科技观察

给我一万字,还说说Java内存不清晰的排查,一万不够就来两万~.~

时间:2023-03-22 12:26:05 科技观察

本文转载自微信公众号“小姐姐的味道”,可以通过以下二维码关注。转载本文请联系味觉小姐公众号。对于内存问题的排查,搞理论是痛苦的,搞实践也是痛苦的。没有干净的地方。为什么?因为内存溢出是Java码农永远的伤。溢出有很多种解释,有全溢出,有缓冲区溢出攻击,还有一种叫做leadership的溢出。不知道溢出理论是什么,xjjdog在这里科普一下。《领导看了会炸毛的溢出理论》最重要的内存溢出是什么?其实,内存溢出就像一场交通事故。造成事故的一方为特定服务;处理事故的一方是相关的程序员。其中最重要的一个环节就是需要在事故现场拍照取证。如果没有照片,没有行车记录仪,没有证据,就只能靠那张嘴,不管怎么说都不可信。**这句话很重要:检查内存问题最重要的是什么?当然是信息收集,留下一些证据来支持我们排查问题。**不要忽视基础知识,并对解决内存问题感兴趣。那是自虐。有很多工具可以帮助我们定位问题,但前提是你离开它。下面这篇文章是xjjdog很久以前留下的。由于标题,大家可能忽略了,但是这些工具可以帮助我们快速定位问题。ss-antp>$DUMP_DIR/ss.dump2>&1netstat-s>$DUMP_DIR/netstat-s.dump2>&1top-Hp$PID-b-n1-c>$DUMP_DIR/top-$PID.dump2>&1sar-nDEV12>$DUMP_DIR/sar-traffic.dump2>&1lsof-p$PID>$DUMP_DIR/lsof-$PID.dumpiostat-x>$DUMP_DIR/iostat.dump2>&1free-h>$DUMP_DIR/free.dump2>&1jstat-gcutil$PID>$DUMP_DIR/jstat-gcutil.dump2>&1jstack$PID>$DUMP_DIR/jstack.dump2>&1jmap-histo$PID>$DUMP_DIR/jmap-histo.dump2>&1jmap-dump:format=b,file=$DUMP_DIR/堆.bin$PID>/dev/null2>&1GClogconfiguration但不是每次出问题都是你在机器这边。手动无法保证实时性能。所以,强烈建议大家输出更详细的GC日志,这样出现问题的时候心情会更舒畅。实际上,这个要求在我看来是强制性的。很多同学上来都说我内存溢出来了。但是你和它需要一些日志信息、堆栈和现场保存的快照。没有什么。这纯粹是为了好玩。下面是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="-X+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,时间,级别"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,这里是它的截图。http://gceasy.io有GC日志是不够的,因为它只记录堆空间的一些变化。至于操作系统的一些资源变化,它无从知晓。所以,如果你有一个监控系统,它也可以在查找问题时提供帮助。从下图中可以看出系统资源的一些变化。溢出示例堆溢出代码。日志。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.OutOfMemoryErrors6/javatumpheid3/javatumping.3.hprofjvisualvm反应。直接内存溢出代码。日志。java-XX:MaxDirectMemorySize=10M-Xmx10MOffHeapOOMTestException在线程"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.io.PrintStream.write(PrintStream.java:526)atjava.io.PrintStream.print(PrintStream.java:597)atjava.io.PrintStream.println线程“main”中的java-Xss128KStackOverflowTestExceptionjava.lang.StackOverflowError(PrintStream.java:736)在StackOverflowTest.a(StackOverflowTest.java:5)哪些代码容易出问题忘记重写hashCode和equals看下面的代码。由于Key类的hashCode和equals方法没有重写。导致所有放入HashMap的对象都无法取出。他们与外界失去了联系。下面的文章详细介绍了它的原理。结果集失控。不要认为这段代码很荒谬。在实际工作的回顾中,xjjdog不止一次地发现了这种痛苦的代码。这可能是由于截止日期,也可能是您刚刚学会编写Java。这行代码踩坑的可能性很大。条件失控代码。与之相似的是病情失控。当某个条件不满足时,就会导致结果集失控。大家可以看看下面的代码,当fullname和other为空时会发生什么?通用参数和一些学生使用各种Objects和HashMaps进行信息交换。这种代码正常运行没有问题,但一旦出错,几乎无法排查。查参数,查堆栈,查调用链,都无效。减少创建大对象频率的一些预防措施:比如传递字节数组时不要缓存过多的堆内数据:使用Guava的弱引用方式查询范围必须可控:比如分库分-表中间件;ES等同样的问题,已经用完的资源一定要关闭:可以使用新的try-with-resources语法,使用lessintern:字符串太长,不能重复使用,会造成内存泄漏.合理的会话超时应该少用。第三方本地代码,使用Java解决方案而不是合理的池大小XML(SAX/DOM),JSON解析要注意对象大小案例分析——这是最常见的情况。知道了这一点,你就可以处理大部分的内存溢出和内存泄漏问题了。症状环境:CentOS7,JDK1.8,SpringBootG1垃圾回收器刚刚启动没有任何问题,慢慢增大体积后,出现OOM,系统自动生成heapdump文件临时解决:重启,问题依旧信息收集log:GClogInformation:内存突然增减,变化很快Stack:ThreadDumpfile:大部分被阻塞在某个方法中压力测试:使用wrk进行压力测试,发现20个并发用户,内存溢出wrk-t20-c20-d300shttp://127.0.0.1:8084/api/testMAT分析栈文件获取:jmap-dump:format=b,file=heap.bin37340jhsdbjmap--binaryheap--pid37340基于eclipse平台开发的MAT工具,本身就是一个Java程序。分析HeapDump文件:发现内存中创建了大量报表对象。通过菜单FindLeaks,一键找到黑李逵。只需按照提示向下挖掘即可。解决分析结果:系统中存在大量数据查询服务,在内存中合并。当并发达到一定程度时,内存中会堆积大量的数据进行计算。解决方案:重构查询服务,减少查询字段数量,使用SQL查询代替内存拼接,避免对结果集进行操作示例:求两个列表的交集案例分析两种现象环境:CentOS7、JDK1.8、JBossCMSgarbagecollector操作系统CPU资源耗尽访问任何接口,响应都很慢分析发现每次GC的效果都特别好,但是非常频繁的了解到使用了heapcache,而且设置的容量比较大,并且缓存填满得非常快!结论:开启一个非常大的缓存后,GC后很快被填满,导致GC常见情况分析三种现象Java进程异常退出Java进程消失不留dump文件。GC日志正常监控发现死亡。堆中的内存占用很少,堆中还有很多空间。分析XX:+HeapDumpOnOutOfMemoryError不起作用监控发现操作系统内存不断增加以下情况会导致程序无响应退出。被操作系统dmesgoom-killerSystem.exit()javacom.cn.AA&终端关闭后kill-9解决方案发现:在dmesg命令中发现确实是被oom-kill解决的:allocatelessmemory给JVM释放空间,为其他进程腾出空间