论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
IT评测·应用市场-qidao123.com
»
论坛
›
软件与程序人生
›
DevOps与敏捷开发
›
JVM实战—OOM的生产案例
JVM实战—OOM的生产案例
郭卫东
论坛元老
|
2025-1-14 12:19:23
|
显示全部楼层
|
阅读模式
楼主
主题
1057
|
帖子
1057
|
积分
3171
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
1.每秒仅上百请求的系统为何会OOM(RPC超时时间设置过长导致QPS翻几倍)
(1)案例背景
在这个案例中,一个每秒仅仅只有100+请求的系统却因频繁OOM而瓦解。这个OOM问题会涉及:Tomcat底层工作原理、Tomcat内核参数的设置、服务请求超时时间。
(2)系统发生OOM的生产现场
某一天突然收到线上系统的一个报警,说系统发生了异常。一看异常信息让人大惊失色,居然是OOM问题。这个问题不得了,因为一旦JVM发生OOM,将会导致系统直接瓦解。OOM代表的是JVM自己已经没办法继续运行了。因此立刻登录到系统的线上机器去查看对应的日志。
需要留意:一旦收到系统OOM报警,或者有人突然反馈说线上系统瓦解了。第一件变乱一定是登录到线上机器去看日志,而不是做别的变乱。当时在机器的日志文件中看到类似下面的一句话:
Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError: Java heap space
复制代码
根据Tomcat的底层工作原理可知:这里的http-nio-8080-exec-1089指的是Tomcat的工作线程,而java.lang.OutOfMemoryError: Java heap space指的是堆内存溢出。
以是连起来看,这段日志的意思就是:Tomcat的工作线程在处理惩罚请求时需要在堆内存里分配对象。但发现堆内存满了,且没办法接纳任何一点多余的对象,以是OOM。
(3)Tomcat的基本工作原理
一.项目摆设的发展
好久从前我们写的系统是摆设在Tomcat中的,我们会在IDEA等开发工具里写一堆的Servlet。然后打包放入Tomcat,之后再启动Tomcat。接着我们会访问Tomcat监听的一个端口号比如8080,然后系统的功能就可以运行起来了。
后来随着技能发展,不再写Servlet这么原始的东西了,有一些类似Spring MVC的框架把Servlet封装起来,于是就基于Spring MVC框架去开发。
再后来技能越来越先辈,出现了Spring Boot,可以把Tomcat之类的Web容器都内嵌在系统里,再后来甚至是基于Spring Cloud去开发分布式的系统。
二.项目摆设在Tomcat的处理惩罚请求流程
如下图示,这是一个最基本的Web系统摆设在Tomcat中运行的架构图:
上面这个图表现:基于Spring Cloud、Spring Boot、Spring MVC等技能,写好一套系统。然后打包后放入线上服务器中摆设的Tomcat目次下,接着启动Tomcat。Tomcat会监听一个默认8080的端口号,之后我们就可以通过欣赏器对这个机器上的Tomcat发起HTTP请求了。
接着Tomcat会监听8080端口收到请求,通常Tomcat会把请求交给Spring Web MVC等框架去处理惩罚。这类框架一般都会在底层封装Servlet/Filter之类的组件去处理惩罚请求,如下图示:
然后类似Spring MVC框架的Servlet组件,就会根据请求的路径去找随处理惩罚这个请求的Controller组件。
三.Tomcat毕竟是什么
假如我们把写好的系统放入Tomcat目次中,然后启动Tomcat。那么启动的Tomcat自己就是一个JVM历程,因为Tomcat是由Java写的。
如下图示,Tomcat自己就是一个JVM历程,我们写好的系统只不过是一些类而已。这些类被Tomcat加载到内存里,然后由Tomcat来执行。
四.Tomcat如何监听8080端口上收到的请求
很简单,因为Tomcat有自己的工作线程。Tomcat有很多自己的工作线程,少则一两百个,多则三四百个。然后从8080端口上收到的请求会被均匀分配给这些工作线程去处理惩罚。这些工作线程收到请求后,就负责调用Spring MVC框架的代码。Spring MVC框架又负责调用我们写好的代码,比如一些Controller类,以是最终的运行流程如下图示:
(4)异常日志
接着查看当时线上系统的日志,发现如下异常:
Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError: Java heap space
复制代码
http-nio-8080-exec-1089其实就是上图中的Tomcat工作线程,它会负责调用Spring MVC及Controller、Service、DAO等代码。以是它发现运行时堆内存不够,就会抛出堆内存溢出的异常。
(5)不要忘了一个关键的JVM参数
一旦发现线上系统发生了OOM内存溢出异常:
第一步是查看日志确定OOM类型和线程
首先需要确定具体的溢出类型,看看到底是堆内存溢出、还是栈内存溢出、或者是Metaspace内存溢出。然后查找哪个线程运行代码时内存溢出,因为Tomcat运行时不仅有自己的工作线程,也有我们创建出的一些线程。
留意:在摆设系统上线的时候务必要设置HeapDumpOnOutOfMemoryError参数,这个参数会在系统OOM内存溢出时导出一份内存快照到指定的位置。
第二步是排查和定位OOM内存溢出问题
主要就是根据系统OOM内存溢出时主动导出的内存快照,通过MAT举行分析。
(6)对内存快照举行分析
MAT分析内存快照的本领:通常在内存溢出时分析内存快照也并没那么复杂,主要就是通过MAT来找到占据内存最大的对象。
一.首先发现占据内存最大的是大量的byte[]数组
大量的byte[]数组就占据了约莫8G左右的内存空间,而当时线上机器给Tomcat的JVM堆内存分配的也就8G的内存而已。因此可以直接得出第一个结论:Tomcat工作线程在处理惩罚请求时会创建大量byte[]数组,大概有8G。大量的byte[]数组直接把JVM堆内存占满了,这就导致继续处理惩罚新请求时,没法继续在堆中分配新对象,从而OOM。如下图示:
二.接下来需要分析,到底是哪些byte[]数组
继续通过MAT深入查看,发现大概是类似下面的一大堆byte[]数组:
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
byte[10008192] @ 0x7aa800000 GET /order/v2 HTTP/1.0-forward...
复制代码
上面只是写出来几个示例而已,其实当时看到了很多类似这样的数组,而且数组大小都是一致的10M。清点了一下,类似上面的数组大概就有800个,也就对应了8G的空间。
三.接着进一步思考,这种数组应该是谁创建的
可以在MAT上可以继续查看一下这个数组是被谁引用的,发现是由Tomcat的类引用的,具体来说是类似下面的这个类:
org.apache.tomcat.util.threads.TaskThread
复制代码
这个类一看就是Tomcat自己的线程类,因此可以认为是Tomcat的线程创建了大量的byte[]数组,然后占据了8G内存。
此时我们发现Tomcat的工作线程大致有400个左右,即每个Tomcat工作线程会创建2个byte[]数组,每个byte[]数组是10M。也就是400个Tomcat工作线程在同时处理惩罚请求,创建了8G的byte[]数组,从而导致内存溢出。如下图示:
(7)系统每秒QPS才100
此时联合上图可知:一秒内来了400个请求,导致Tomcat的400个工作线程全部处理惩罚请求。每个工作线程在处理惩罚一个请求时,会创建2个数组,每个数组是10M,结果导致瞬间让8G的内存空间被占满了。
看起来根据MAT信息,分析出来的生产系统的情况就是如此。但是检查了一下系统的监控,发现每秒请求并不是400,而是100。
问题一:为何Tomcat的400个线程都处于工作状态
明明每秒才100个请求,为什么Tomcat的400个线程都处于工作状态?出现这种情况只有一种大概,那就是每个请求处理惩罚需要耗费4秒时间。假如每秒来100个请求,但是每个请求处理惩罚完毕需要4秒的时间。那么这会导致,在4秒内的某个瞬间就会有400个请求需要同时处理惩罚。从而也就会导致Tomcat的400个工作线程都在工作,出现上述情况。
问题二:为何Tomcat工作线程在处理惩罚一个请求时会创建2个10M的数组
这里可以到Tomcat的配置文件里搜索一下,发现了如下的一个配置:
max-http-header-size: 10000000
复制代码
这个配置会导致Tomcat工作线程在处理惩罚请求时创建2个数组,每个数组的大小就是上面配置的10M。
因此可以总结一下系统运行时的场景了:每秒100个请求,每个请求处理惩罚需要4秒。导致4秒内的某个瞬间,400个请求会同时被400个线程处理惩罚。每个线程根据配置创建2个数组,每个数组10M,于是占满了8G的内存。如下图示:
(8)为什么处理惩罚一个请求需要4秒
这个问题绝对是偶发性的,不是每次处理惩罚请求都如此。因为这个系统的负责人说,寻常处理惩罚一个请求也就几百毫秒而已。既然如此,那么唯一办法只能在日志里去找了,查看变乱发生时的日志。发现日志中除了OOM外,还有大量的服务请求调用超时的异常,如下:
Timeout Exception...
复制代码
也就是说,这个系统在通过RPC调用其他系统时出现大量的请求超时。立刻翻看一下系统的RPC调用超时配置,惊讶地发现:这个系统的负责人将服务RPC调用超时时间设置了刚好是4秒。
也就是说,在这个时间里远程服务自己故障了,然后导致我们的系统RPC调用时访问不通。于是就会在配置好的4秒超时时间后抛出异常,在这4秒内,工作线程会直接卡死在无效的网络访问上。如下图示:
上图已经表述很清楚了:之以是每个请求要处理惩罚4秒,是因为鄙俚服务故障了,网络请求失败了。此时会按设置好的4秒超时时间不停卡住4秒后才会抛出Timeout异常。然后请求才会处理惩罚结束,这就是一个请求处理惩罚需要4秒的根本原因。处理惩罚一个请求4秒会导致4秒内积压400请求同时在处理惩罚(每秒100请求),最后导致400个工作线程创建800个10M的数组,耗尽8G内存而OOM。
(9)对系统举行优化
上述问题原因就是超时时间设置得太长了,因此将超时时间改为1秒。这样每秒100个请求,只有200个数组占据2G内存,不会占满堆内存。然后1秒内这100个请求会全部超时,请求就处理惩罚结束。下一秒再来100个请求又是新一轮处理惩罚,不会出现每秒积压100个请求,4秒内出现400个请求同时处理惩罚的情况。
别的,对Tomcat的参数,max-http-header-size可以适当调节小一些。这样Tomcat工作线程自身为请求创建的数组,不会占据太大的内存空间。
2.Jetty服务器的NIO机制如何导致堆外内存溢出(S区太小 + 禁NIO的显式GC)
(1)案例背景
利用Jetty作为Web服务器时在某个罕见场景下发生的一次堆外内存溢出,这种场景其实并不多见,但可以学习这种场景的排查方法。
(2)案例发生现场
有一天突然收到线上的一个报警:某台机器摆设的一个服务突然间就不可以访问了。此时第一反应固然是立刻登录机器去看一下日志,因为服务挂掉,很大概是OOM导致的,固然也大概是其他原因导致的。这个时候在机器的日志中发现了如下的一些核心信息:
nio handle failed java.lang.OutOfMemoryError: Direct buffer memory
at org.eclipse.jetty.io.nio.xxxx
at org.eclipse.jetty.io.nio.xxxx
at org.eclipse.jetty.io.nio.xxxx
复制代码
上述日志告诉我们有OOM异常,但是是哪个地区导致的呢?居然是Direct buffer memory地区,且后面还有Jetty相干的方法调用栈。到此为止,根据这些日志基本就可以分析出这次OOM发生原因了。
(3)开端分析变乱发生的原因
Direct buffer memory其实就是堆外内存,堆外内存是JVM堆内存外的一块内存空间,这块内存不是JVM管理的。Java代码在JVM堆之外利用的内存空间,就叫Direct buffer memory。
假如按英笔墨面理解,它的意思就是直接内存。以是从字面就可以看出,这块内存并不是JVM管理的,而是OS管理的。
Jetty可以理解为是一个和Tomcat一样的Web容器,它也是Java写的。写好的一个系统,可以打包后放入Jetty,然后启动Jetty即可。Jetty启动后自己就是一个JVM历程,它会监听一个端口号,比如9090。接着就可以向Jetty监听的9090端口发送请求,Jetty会把请求转交给我们用的Spring MVC之类的框架,而Spring MVC之类的框架再去调用我们写好的Controller之类的代码。
Jetty作为一个JVM历程运行我们写好的系统的流程,如下图示:
首先可以明确的是:这次OOM是Jetty这个Web服务器在利用堆外内存时导致的。以是基本可以推测出,Jetty服务器大概在不停地利用堆外内存。然后堆外内存空间不足,没法利用更多的堆外内存,此时就抛出OOM。如下图示:
(4)解决OOM问题的技能储备建议
Jetty是如何去JVM堆外申请一块堆外内存来利用的?然后这个堆外内存又是如何释放的?
这就涉及到Java的NIO底层技能细节,假如对NIO不相识,突然看到这个异常,估计是没法继续分析下去了。因此JVM性能优化相对还是较为容易,而且按照整个套路没太大难度。但假如要解决OOM问题,那么除了一些特别简单的模仿内存溢出,其他很多时候生产环境下的OOM问题,都是有技能难度的。
1000个工程师大概会遇到1000种差别的OOM问题,大概排查的思路是类似的,或者解决问题的思路是类似的。但是假如要解决各种OOM问题,那么需要对各种技能都有一定的相识。换句话说,需要有较为踏实的技能内功修为。
比如上面那个案例,就需要相识Tomcat的工作原理,才气分析清楚。同样这里的案例,也要求相识Java NIO技能的工作原理才气分析清楚。
以是需要对其他的核心主流技能做一些深层次的甚至源码级别的研究,这些底层技能积聚会在线上系统出现问题时,资助我们分析和解决问题。
(5)堆外内存是如何申请的和如何释放的
一.堆外内存是如何分配的
假如在Java代码申请利用一块堆外内存空间,要利用DirectByteBuffer。可通过这个类构建DirectByteBuffer对象,这个对象会在JVM堆内存里。构建该对象同时,会在堆外内存中划出一块内存空间与该对象关联起来。这就是分配堆外内存时的大致思路,如下图示:
二.堆外内存是如何释放的
当DirectByteBuffer对象没被引用成了垃圾对象后,天然会在某一次YGC或者FGC时把该DirectByteBuffer对象接纳掉。只要接纳一个DirectByteBuffer对象,就会释放掉其关联的堆外内存,如下图示:
(6)为什么会出现堆外内存溢出的情况
一般什么情况下会出现堆外内存的溢出?假如创建了很多DirectByteBuffer对象,那么就会占用大量的堆外内存。假如DirectByteBuffer对象没被GC线程接纳,则堆外内存就不会释放。
久而久之,当堆外内存已经被大量的DirectByteBuffer对象关联利用后。假如要再创建DirectByteBuffer对象利用更多堆外内存,那么就会OOM。
什么情况会出现大量DirectByteBuffer对象存活,使堆外内存无法释放?有一种大概就是系统正在承载超高并发,负载压力很高。瞬时大量请求过来,创建过多的DirectByteBuffer占用了大量堆外内存。此时再继续想要利用堆外内存,就会内存溢出。但是这个系统是这种情况吗?显着不是。因为这个系统的负载其实没有那么高,也没有大量的瞬时请求过来。
(7)真正的堆外内存溢出原因分析
这时就得思路活跃起来了,我们完全可以去用jstat等工具观察一下线上系统的现实运行情况,同时根据日志看看一些请求的处理惩罚耗时,综合性地举行分析。当时我们通过jstat工具分析JVM运行情况,同时分析过往的GC日志。别的还看了系统各个接口的调用耗时后,分析出了如下结论。
一.首先看了接口的调用耗时
这个系统并发量不高,但是它每个请求处理惩罚较为耗时,平均每个请求需要一秒多的时间去处理惩罚。
二.然后通过jstat发现
随着系统不停地运行,会不停创建各种对象,包括Jetty自己会不停地创建DirectByteBuffer对象,直到新生代的Eden区满了,就会触发YGC。
但往往在举行YGC垃圾接纳的一瞬间,大概有的请求还没处理惩罚完毕。此时就会有不少DirectByteBuffer对象处于存活状态,不能被接纳掉。固然之前不少DirectByteBuffer对象对应的请求大概处理惩罚完毕了,它们就可以被接纳。以是肯定会有一些DirectByteBuffer对象是处于存活状态的,那么这些存活状态下的对象就需要转入Survivor地区中。
但这个系统当时在上线时,内存分配不合理。年轻代只分配一两百M的空间,老年代反而给七八百M的空间,进而导致新生代中的Survivor地区只有10M左右的空间。而在YGC过后,一些存活下来的对象(包括DirectByteBuffer)会超过10M,于是这些存活的对象就没法放入Survivor中,只能直接进入老年代。
上述过程反复执行,逐步就会导致一些DirectByteBuffer对象进老年代。老年代的DirectByteBuffer对象越来越多,而且都关联很多堆外内存。这些老年代里的DirectByteBuffer其实很多都是可以接纳的了。但是因为老年代不停没塞满,以是没有触发FGC,天然也就不会接纳老年代里的这些DirectByteBuffer对象。而老年代里这些没有被接纳的DirectByteBuffer对象,却又不停与占据了大量的堆外内存空间关联着。
直到最后,当系统要继续利用堆外内存时:发现全部堆外内存都被老年代里大量的DirectByteBuffer对象给占用了。虽然它们可以被接纳,但是无奈因为始终没有触发老年代的FGC。以是堆外内存也始终无法被接纳掉,最后就导致堆外内存溢出问题。
(8)岂非Java NIO就没考虑过这个问题吗
先不说如何解决这个问题,岂非Java NIO就没考虑过会有上述问题吗?固然不是了,Java NIO是考虑到的。它知道大概会有很多DirectByteBuffer对象也许没被引用了,但是因为没有触发GC就导致它们不停占据着堆外内存。
以是在Java NIO的源码中会做如下处理惩罚:每次分配新的堆外内存发现不足时,都会调用System.gc(),以此提示JVM执行一下GC去接纳那些没被引用的DirectByteBuffer对象,从而释放堆外内存空间。
只要能触发垃圾接纳去接纳一些没被引用的DirectByteBuffer对象,那么就会释放一些堆外内存,天然就可分配更多对象到堆外内存去了。
惋惜在JVM中设置了如下参数,该参数会导致System.gc()不收效,因此才导致上述情况。
-XX:+DisableExplicitGC
复制代码
(9)最终对问题的优化
其实项目问题有两个:
一是内存设置不合理导致DirectByteBuffer对象进入老年代,导致堆外内存不停释放不掉。
二是设置了参数-XX:+DisableExplicitGC禁用了System.gc(),导致Java NIO没法显式触发去接纳一些垃圾DIrectByteBuffer对象,导致堆外内存不停释放不掉。
因此需要做的变乱就是:
一是合理分配内存,给年轻代更多内存,让Survivor地区有更大的空间。
二是放开参数-XX:+DisableExplicitGC这个限制,让System.gc()收效。
做完优化后,DirectByteBuffer对象一般就不会不断进入老年代了。只要它停留在年轻代,YGC后就会被正常接纳,然后释放堆外内存。别的,只要放开对-XX:+DisableExplicitGC的限制。那么Java NIO发现堆外内存不足,就能通过System.gc()显式举行FGC。接纳掉进入了老年代的一些DirectByteBuffer对象来释放一些堆外内存。
3.一次微服务架构下的RPC调用引发的OOM故障排查实践(MAT案例)
(1)解决系统OOM的核心能力
解决系统OOM故障的核心能力:必须对自己的线上系统,所利用的各种技能源码最好都要有深入理解,从服务框架到第三方框架、到Tomcat/Jetty等Web服务器、再到各种底层中间件系统。
因为一般线上系统OOM都不是简单地由我们写的代码导致的,往往都是因为系统利用了某个开源技能,该技能的内核代码有一定的故障和缺陷而导致的。这时要解决OOM问题,就必须深入到源码中去分析。
以是这里只能介绍各种真实的生产案例以及分析思路,真正到了具体的OOM故障时,由于每个系统的故障都是差别的。需要我们分析对应的技能底层,然后找到故障原因,最后解决故障。
(2)系统架构介绍
这个系统是比较早的一个系统,在举行服务间的RPC通信时,接纳的是基于Thrift框架封装的RPC框架。然后公司的系统,就是基于这个RPC框架去举行通信。下图是一个最基本的服务间RPC调用的示例:
(3)故障发生现场
寻常都是服务A通过RPC框架去调用服务B,但有一天服务A更新代码上线后发现没什么问题,但服务B却宕机了。明明修改代码和重新摆设的是服务A,为什么造成服务B挂掉呢?如下图示:
登录到服务B的机器去查看服务B的日志,结果在里面发现了OOM异常,直接表现出堆内存溢出了;
java.lang.OutOfMemoryError Java heap space
复制代码
可服务B为什么会OOM呢?岂非是服务B的问题?那不如重启服务B?于是我们尝试重启了服务B。结果发现服务B重启过后很快又宕机了,而且原因还是OOM。这就希奇了,因为服务A修改代码摆设前,服务B从来没出现过这种情况。如今却是服务A修改了代码摆设后才导致服务B出现了这种情况。
(4)开端查找内存溢出的故障发生点
一般内存溢出时,务必要先确认一下故障的发生点。这个通过查看日志就可以确认,因为在日志中都会有详细的异常栈信息。通过查看日志发现,引发OOM异常的,居然就是那个自研的RPC框架,当时的异常信息大概如下所示:
java.lang.OutOfMemoryError: Java heap space
xx.xx.xx.rpc.xx.XXXClass.read()
xx.xx.xx.rpc.xx.XXXClass.xxMethod()
xx.xx.xx.rpc.xx.XXXClass.xxMethod()
复制代码
由此可以开端确定,是自研的RPC框架在接收请求时引发了OOM,如下图示:
(5)分析内存快照找到占用内存最大的对象
既然已经定位到OOM故障发生的位置了,也就是谁引发的OOM。接下来用MAT分析一下发生OOM时,对内存占用最大的是哪个对象了。
打开OOM时的内存快照,发现占用内存最大的是一个超大的byte[]数组。当时一台机器给的堆内存也不过就是4G而已,而在内存快照中发现一个超大的byte[]数组,就已占据了差不多4G空间。
这个byte[]数组是哪儿来的?通过分析该byte[]数组的引用,会发现就是由RPC框架内部的类引用的。MAT里右击一个对象会出现一个菜单,里面有个选项就可以查看引用者。如下图示:
(6)通过分析源代码找出原因
一.整个分析流程步骤
第一步先定位到是谁导致的OOM
这时往往大概是由于某种技能,比如Tomcat、Jetty、或者是RPC框架。
第二步一般利用MAT等工具分析内存快照
找到当时占用内存最大的对象是谁,可以找找都是谁在引用这个对象,固然一般在第一步通过看日志的异常栈就大概知道导致OOM的类是谁。
第三步需要得对那个技能的源代码举行分析
比如对Tomcat、RPC框架源代码举行追踪分析。
一般分析到这一步,答案几乎将近揭晓了,通过代码分析找到故障发生原因。假如大概的话,最好在本地搭建类似的环境,把线上问题给复现出来,接下来就联合日志里的异常栈分析这个RPC框架的源代码。
二.这个RPC框架在接收请求时的流程
首先在服务A发送请求时,会对传输过来的对象举行序列化。这个序列化,就是把类似Request的对象变成一个byte[]数组。
然后对服务B而言,它首先会根据自界说的序列化协议,对发送过来的数据举行反序列化。接着把请求数据读取到一个byte[]缓存,然后调用业务代码处理惩罚请求。最后请求处理惩罚完毕,清算byte[]缓存。
以上便是这个RPC框架运行的原理了,接着需要在源码中要探求一下:为什么用来缓冲请求的byte[]数组会达到几个G那么大?正常情况下,这个用来缓冲请求的byte[]数组应该最多不超过1M的。
(7)一个关键点——RPC框架的类界说
通过源码的分析,我们最终总算搞清楚了,原来当时有这么一个特别的情况:因为RPC框架要举行对象传输,就必须让服务A和服务B都知道有该对象。举个例子,比如服务A要把一个Request对象传输给服务B。那么首先需要利用一种特定的语法界说一个对象文件,大概类似如下:
syntax = "proto3";
message Request {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
复制代码
然后会通过上面特别语法写的文件可以反向生成一个对应的Java类,此时会生成一个Java语法的如下Request类:
public class Request {
//这里是一堆的自动生成的代码
}
复制代码
接着这个Request类需要在服务A和服务B的工程里都要引入,这样服务A和服务B就知道:把Request交给服务A,它会自己举行序列化成字节流。然后到服务B时,它会把字节流反序列化成一个Request对象。如下图示:
服务A和服务B都必须知道有Request类的存在,才气把Request对象序列化成字节流,才气从字节流反序列化出一个Request类对象。
(8)RPC框架的一个Bug——过大的默认值
在上图中,服务B在接收到请求后,会先反序列化,接着把请求读出来放入一个byte[]数组。
但是这里RPC框架有一个Bug,就是一旦发现发送过来的字节流反序列化失败,就会开发一个byte[]数组。这个byte[]数组默认4G,会把发送方的字节流原封不动地放进去。
而出现对字节流反序列化失败的原因是服务A修改了Request类,但服务B不知这次修改,Request是旧版本。比如服务A的Request类有15个字段,序列化成字节流发送给服务B了。但服务B的Request类只有10个字段,那么反序列化的时候就会失败。
以是最终的问题就出在这里了:当时服务A修改了很多Request类的字段,结果没告诉服务B。以是服务A上线后,服务B没能反序列化传过来的序列化的Request对象。此时服务B就会开发一个默认4G的byte[]数组存放数据,然后导致OOM。如下图示:
(9)最终的解决方案
当时为什么要把异常情况下的数组默认大小设置为几个G那么大?这个其实也没办法,因为当时写这段代码的刚好是新手工程师。考虑反序列化失败,就原封不动封装字节流到数组,由对方自行处理惩罚。由于不知道字节流里有多少数据,以是就开发一个大数组,保证能放下。且一般测试时又不会测到这种异常情况,以是之前不停没发现这个问题。
其实解决这个问题的办法很简单,把RPC框架中那个数组的默认值从4G调整为4M即可。一般请求都不会超过4M,不需要开发那么大的数组,别的就是让服务A和服务B的Request类界说保持一致即可。
4.一次没有WHERE条件的SQL语句引发的OOM问题排查实践(利用MAT本领)
(1)案例背景引入
在利用Mybatis写SQL语句时,在某些情况下不加where条件也可执行,结果导致一下子查询出上百万条数据引发系统OOM。这个案例自己比较简单,不涉及太多其他的技能问题。下面通过这个案例来介绍利用MAT对线上内存快照举行分析的本领。
(2)关于MAT工具对OOM故障的实践意义
假如系统触发OOM是由于Tomcat、Jetty、RPC框架之类的底层技能,那么MAT对这种系统来说用处并不是那么大。
因为最多就是用MAT找一找占用内存过多的对象,然后联合异常日志调用栈和MAT中的对象引用情况,开端定位是底层技能中的哪部门代码导致的内存溢出。
假如要真正解决这种内存溢出问题,还得去细致研究Tomcat、Jetty、RPC框架之类技能的底层源码,然后联合线上系统的负载情况、访问压力、GC情况,以及底层技能的源码细节,才气分析清楚。
假如OOM是由于自己系统代码问题导致的,那么就容易解决得多了。只要根据MAT层层分析,就可以定位到代码的问题所在。毕竟自己写的代码自己是最熟悉的,以是很快就可以解决问题。
(3)故障发生现场
某天突然收到反馈说线上一个系统瓦解不可用了,此时立刻登录到线上机器去查看日志。在日志中果然发现了OOM的异常,堆内存溢出了:
java.lang.OutOfMemoryError,java heap space
复制代码
那么下一步就是把主动导出的内存快照拷贝到电脑上,用MAT去分析。下面介绍如何奇妙的用MAT工具迅速定位问题代码。
(4)第一步检查内存中到底什么对象太多了
这时可以用MAT中的Histogram功能去检查占用内存最多的对象有哪些。固然MAT的那个内存走漏报告的利用,那个功能也是没问题也很好用。但这里介绍别的一套分析思路,其实原理都是类似的。
分析内存快照,就是要找到占用内存最大的对象。然后找到谁在引用这个对象,是哪个线程在引用。接着找到创建这些对象的相干代码和方法,这样就可以到对应的源码里去分析问题了。
在如下图中,红圈处那个按钮就是Histogram按钮,点击过后就会进入别的一个界面。
接着进入Histogram界面,如下图示:
在这个界面中,就可以看到是谁占用过多内存了。比如这里显着是Demo1$Data这个内部类占用了过多内存,而且这里表现Demo1$Data对象有10000个,此时我们就会想看看这些Demo1$Data对象都是什么?
(5)第二步深入查看占用内存过多的对象
为了查看这些Demo1$Data对象都会是什么?此时进入第二步,即查看占用内存过多的对象是被谁引用、哪个线程引用、里面都是什么。也就是按照下图,点击红圈处的按钮:
点击上图那个红圈处的按钮之后,就会进入一个dominator_tree的界面,dominator_tree界面会展示出当前JVM中全部的线程。如下图示:
在dominator_tree界面可以清晰看到哪些线程创建了过多的对象,比如排第一的线程是:java.lang.Thread @ 0x5c0020838 main Thread。这就表明白是一个main线程创建了过多的对象,那接下来就可以直接睁开这个线程,看看它到底创建了哪些对象。如下图示:
当睁开main Thread后,发现有一个java.util.ArrayList @ 0x5c00206a8,说明main线程创建了一个巨大的ArrayList。于是继续睁开这个ArrayList,发现里面是一个java.lang.Object[]数组。于是继续睁开,就会看到大量的Demo1$Data对象了。至此真相明确。
其实在dominator_tree界面就能很快找到是哪个线程创建了过多的对象,而且通过层层睁开,可以看到这个线程创建了哪些对象太多了,这样就和之前的Histogram界面中占用内存最多的Demo1$Data对上了,而且这里可以轻易看到每个Demo1$Data对象的详细的情况。
(6)生产案例的追踪
当时对于那个线上生产案例而言,追踪到dominator_tree界面这里,发现某个Tomcat的工作线程创建了一大堆的java.lang.HashMap。那么这些java.lang.HashMap中是什么呢?
我们发现全都是各种从数据库里查出来的字段,因为只要睁开那个HashMap就能看到查出来放入内存的全部数据。
以是看到这一步基本就很明确了:就是工作线程处理惩罚一个请求时,发起了一个SQL查询,查出大量的数据。每条数据是一个HashMap,就是这大量的数据导致了系统的OOM。
(7)第三步查找哪行代码创建了这么多对象
找到占用内存最大的对象后,最后一步就是要定位是哪一行代码或者是哪个方法创建了那么多对象。这时又需要别的一个视图了,如下图的红圈处所示:
点击上图红圈的按钮会进入一个thread_overview界面,下图展示出JVM中全部的线程以及每个线程当时的方法调用栈,以及每个方法创建了哪些对象。
从上图可直接看到一个main Thread,它先执行了一个Demo1.java类中的第12行处的一个Demo1.main()方法,接着main方法又执行了一个java.lang.Thread类的sleep()方法。任何一个线程在此时此刻执行和调用了哪些方法,都会在这里表现出来。
我们接着睁开上图中的Demo1.main()方法,就可以看到线程调用每个方法的时候创建和引用了哪些对象。如下图示:
在上图中,我们发现Demo1.main方法执行的时候创建了一个ArrayList,睁开发现是一个java.lang.Object[]数组,再次睁开发现就是一大堆的Demo1$Data对象。到此真相明确,一清二楚。
通过上述步骤,我们就可以快速的定位出来占用内存过多的对象,以及是哪个线程创建了这些对象,线程执行哪个方法时创建了这些对象,每个对象的细节我们都可以看得清清楚楚。
(8)继续对生产案例举行追踪
接纳上述方法,对生产案例举行追踪,定位到系统中的一个业务方法。该方法在执行查询操作时,因没带WHERE条件,查询出上百万数据,最后导致内存溢出。因此对那个方法对应的SQL语句举行修改即可。
MAT利用本领总结:
一.首先通过Histogram界面找占用大的对象
二.然后进入dominator_tree界面找对应的线程
三.接着进入thread_overview界面找线程对应的方法调用栈
5.每天10亿数据的日志分析系统的OOM问题排查(堆内存太小 + 递归要审慎)
(1)案例背景
这个案例背景是一个每天10亿数据量的日志清洗系统,这个系统做的变乱非常简单,主要是从Kafka中不停消费各种日志数据,然后对日志的格式举行清洗。比如对一些涉及到用户敏感信息的字段(姓名、手机号)举行脱敏处理惩罚,然后把清洗后的数据交付给其他的系统去利用,比如推荐系统、广告系统、分析系统都会去利用这些清洗好的数据。如下图示:
(2)变乱发生现场
某天突然收到线上的报警,发现日志清洗系统发生了OOM异常,登陆到线上机器查看日志后,发现还是那么经典的堆内存溢出问题。
java.lang.OutOfMemoryError: java heap space
复制代码
于是便来分析一下问题到底出在哪里了。首先看异常日志,定位一下到底是谁导致的这个问题,当时我们在日志里大致看到了类似如下的一些信息:
java.lang.OutOfMemoryError: java heap space
xx.xx.xx.log.clean.XXClass.process()
xx.xx.xx.log.clean.XXClass.xx()
xx.xx.xx.log.clean.XXClass.xx()
xx.xx.xx.log.clean.XXClass.process()
xx.xx.xx.log.clean.XXClass.xx()
xx.xx.xx.log.clean.XXClass.xx()
xx.xx.xx.log.clean.XXClass.process()
xx.xx.xx.log.clean.XXClass.xx()
xx.xx.xx.log.clean.XXClass.xx()
复制代码
从日志中可以很显着发现:同样的一个方法(XXClass.process())反复出现了多次,最终导致OOM。
这时通过日志,有履历的大概已经可以发现一个问题了。那就是在某一处代码出现了大量的递归操作,正是大量的递归操作,反复调用一个方法后,导致了堆内存溢出问题。
开端定位是XXClass.process()方法出现了递归操作导致OOM,接下来用MAT分析一下内存快照。
(3)开端分析内存快照
接着开始分析生产现场的内存快照,在分析MAT时发现了一个问题:因为有大量的XXClass.process()方法递归执行,每个XXClass.process()方法中都创建了大量的char数组,最后因为XXClass.process()方法又多次递归调用,也就导致了大量的char[]数组耗尽了内存。如下图示,每次调用都创建大量char[]数组导致内存溢出。
(4)问题在JVM参数上
基本定位出问题所在了,但当时还发现了另一个比较大的问题。虽然XXClass.process()方法递归调用了多次,但现实上在MAT中发现递归调用的次数也并不多,大概就几十次递归,而且全部递归调用加起来创建的char[]数组对象总和也就最多1G而已。
假如是这样的话,其实我们应该先留意一个问题,那就是大概这次OOM的发生不一定是代码写得有多差。大概就是JVM的内存参数设置的不对,给堆内存分配的空间太小了。
(5)分析一下JVM的GC日志
为了确认堆内存到底是不是设置太小了,就得分析发生GC的具体过程。如今系统已经宕机,我们唯一可以看到JVM启动参数的,就是GC日志。从GC日志中,可以看到JVM启动时的完整参数设置的,核心的内容如下:
-Xmx1024m -Xms1024m
-XX:+PrintGCDetails -XX:+PrintGC()
-XX:+HeapDumpOnOutOfMemoryError -Xloggc:/opt/logs/gc.log -XX:HeapDumpPath=/opt/logs/dump
复制代码
这里的参数主要是把GC日志详细记录在了一个日志文件里。别的指定了内存溢出时要导出内存快照,还有就是堆内存给1G,但是这台机器可是4核8G的。
接着我们看一下当时记录下来的gc.log日志:
[Full GC (Allocation Failure) 866M->654M(1024M)]
[Full GC (Allocation Failure) 843M->633M(1024M)]
[Full GC (Allocation Failure) 855M->621M(1024M)]
[Full GC (Allocation Failure) 878M->612M(1024M)]
复制代码
上面把GC日志中大量无关的信息省略掉了,因为和我们分析关系不大。从上面的GC日志可以发现:Allocation Failure触发的FGC很多,每次FGC都只能接纳一点点对象。别的日志里表现的是每秒都会执行一次FGC,这就很可怕了。
以是基本上可以明确GC的具体过程如下:
一.XXClass.process()方法递归创建大量char[]数组,导致堆内存满了。
二.然后导致连续一段时间,每秒触发一次FGC。因为内存都满了,特别是老年代几乎都满了。以是大概是每秒执行YGC前,发现老年代可用空间不足,提前触发FGC。也大概是YGC存活对象太多无法放入S区,老年代又放不下,只能FGC。
三.每次FGC只能接纳少量对象,直到某一次FGC接纳不到任何对象。然后新的对象无法放入堆内存,此时就会触发内存溢出的异常,如下图示:
因此堆内存肯定是偏小了,从而导致频繁的FGC。
(6)分析一下JVM运行时内存利用模型
接着我们用jstat分析一下当时JVM运行时的内存模型。当时重启了系统,每秒打印一次jstat的统计信息,就看到了下面的情况:
S0 S1 E O YGC FGC
0 100 57 69 36 0
0 100 57 69 36 0
0 100 65 69 37 0
0 100 0 99 37 0
0 100 0 87 37 1
复制代码
一.刚开始都是新生代的Eden区在涨。
二.接着YGC从36到37,就发生了一次YGC。
三.然后Old区从占比69%到99%,说明YGC后存活对象太多,Survivor区放不下,直接进老年代了。
四.接着老年代都占了99%了,直接就触发了一次FGC。但这次FGC仅让老年代从占比99%降到87%而已,接纳了少量的对象。
五.上面的那个过程反复循环几次,新生代的对象反复进入老年代,不停触发FGC,但接纳不了多少对象。几次循环过后,老年代满了,大概FGC没接纳多少对象。新的对象又放不下,就触发OOM了。
(7)优化第一步:增加堆内存大小
以是这个OOM的问题,就是内存分配不足的问题。因此第一步,给堆内存加大空间,给堆内存5G的空间(4核8G机器)。接着运行系统,通过jstat观察,就可以发现:每次YGC过后存活对象都落入S区了,不会随便进入老年代,而且因为堆内存很大,基本上运行一段时间不会发生OOM问题了。
(8)优化第二步:改写代码
别的就是改写代码,让它不要占用过多的内存。代码之以是递归,就是因为在一条日志中,大概会出现很多用户的信息。一条日志也许会合并包含了十几个到几十个用户的信息,这时代码中就会递归十几次到几十次去处理惩罚这个日志。每次递归都会产生大量的char[]数组,这些数组是来自切割了的日志,用来后续处理惩罚的。
其实这个代码写的完全没有必要,因为对每一条日志,假如发现包含了多个用户的信息。其实就对这一条日志切割出来举行处理惩罚即可,完全没有必要递归调用。每次调用都切割一次日志,生成大量的char[]数组。这一步代码优化后,便发现线上系统的内存利用情况降低了10倍以上。
以是JVM有风险,递归要审慎。
6.类加载器过多引发OOM问题排查(服务假死自己恢复+ top判定 + OS杀历程)
(1)案例背景
公司里有一个非常正常的线上的服务,接纳的是Web系统摆设在Tomcat的方式来启动的。但是有一段时间,经常会收到反馈说这个服务非常不稳定,访问这个服务的接口会出现假死问题。也就是一段时间内无法访问这个服务的接口,但过一会又可以访问了。
(2)利用top命令检查机器资源利用
因为当时的情况是服务假死,接口无法调用,并不是OOM之类的异常。以是此时也很难直接去看它的线上日志,根据日志立刻举行问题定位。
因此针对服务假死这个问题:首先可以用top命令去检查一下机器的资源利用量,通过top命令查看机器上运行的各历程对CPU和内存两种资源的利用量。
一.接口假死的两者情况
为什么要先用top看机器资源?因为假如服务出现无法调用接口假死的情况,首先要考虑的是两种原因。
第一种原因:这个服务大概利用了大量的内存,内存始终无法释放,导致频繁FGC。也许每秒都执行一次FGC,结果每次都接纳不了,最终导致频繁FGC。频繁FGC又会频繁Stop the World,以是接口调用时就会出现频繁假死。
第二种原因:大概是这台机器的CPU负载太高了,也许是某个历程耗尽了CPU资源。CPU资源耗尽会导致这个服务的线程始终无法得到CPU资源去执行。没有CPU资源去执行也就无法相应接口调用请求,从而频繁假死。
因此针对服务假死的问题,先通过top命令查看,就知道关键点了。
二.根据JVM历程对CPU斲丧和内存的斲丧判定是否发生频繁GC
针对线上这台机器利用top命令检查之后,就发现了一个问题:这个服务的历程对CPU耗费很少,仅仅耗费了1%的CPU资源。但是这个历程虽然耗费1%CPU,却耗费了50%以上的内存资源,这个就引起了我们的留意。
因为这台机器是4核8G的尺度线上假造机,针对这种机器通常会给摆设的服务的JVM分配5~6G总内存。撤除Metaspace区,堆内存大概会给到4G~5G,毕竟还得给创建大量的线程留下一部门内存。
由于JVM利用的内存主要是三类:栈内存、堆内存和Metaspace地区,如今给Metaspace地区512M以上的空间、堆内存大概有4G。每个线程的栈内存给1M,JVM历程有几百上千线程,也要将近1G内存。
此时JVM服务历程对内存耗费已超过50%,说明它几乎快把分配给它的内存斲丧尽。而且最关键的是,它恒久保持对内存资源的斲丧在50%以上甚至更高,这说明JVM在GC时并没有把内存接纳掉。
(3)当内存利用过高时会发生什么
一.JVM历程对内存利用率过高的大概原因
既然这个服务的历程对内存利用率这么高,大概发生的问题也就三种:
第一种是内存利用率居高不下,导致频繁FGC
第二种是内存利用过多,导致OOM内存溢出
第三种是内存利用率过高,导致JVM历程被OS杀掉
二.分析这是属于哪种原因导致内存利用率过高
首先分析第一种情况,于是利用jstat分析一下JVM运行的情况。发现内存利用率确实很高,也确实经常发生GC。但现实上GC耗时每次也就几百毫秒,并没有耗费过多的时间。也就是说虽然GC频率高,但是其实是个常规现象。而且这个服务虽然经常频繁GC,但也没听见上游服务反馈说服务假死。因此第一种情况可以直接排撤除了。
接着分析第二种情况,岂非是JVM自己偶然候发生OOM挂掉了?挂掉时一定导致服务无法访问,上游服务肯定会反馈说我们服务死掉的。但是检查应用服务自身日志,并没有看到任何日志输出OOM异常。
接着分析第三种情况,也就是JVM运行时要申请的内存过多。结果内存不足,偶然OS会直接杀掉这个历程。大概在历程被杀掉的过程中,就出现了上游服务无法访问的情况。而该机器上的JVM历程是有监控脚本的:一旦JVM历程被杀掉,会有脚本主动把JVM历程重新启动拉起来。以是也许其他服务过一会就会发现,服务又可以访问了。
(4)到底是谁占用了过多的内存
假如要解决这个问题,就必须要找出到底是什么对象占用内存过多,进而申请过多的内存,最后导致历程被杀掉了。很简单,直接从线上导出一份内存快照即可。我们在线上系统运行一段时间后,用top命令和jstat命令观察一段时间,发现当JVM已耗费超50%内存时,迅速导出一份内存快照举行分析。
此时用MAT举行内存快照分析时发现,存在一大堆的ClassLoader也就是类加载器,大概有几千个。而且这些类加载器加载的东西,都是大量的byte[]数组,这些一共占用了超过50%的内存。
那么这些ClassLoader是哪儿来的?为什么会加载那么多的byte[]数组?其实除了可以用类加载器加载类,还可以用类加载器加载一些其他资源。比如可以用类加载器加载一些外部配置文件。
当时写这个系统代码的工程师做了自界说类加载器,而且在代码里没限制地创建大量自界说类加载器,重复加载大量数据。结果经常一下子就把内存耗尽了,历程就被杀掉了。
因此解决这个问题非常的简单,直接就是修改代码,避免重复创建几千个自界说类加载器,避免重复加载大量数据到内存。
7.数据同步系统频繁OOM内存溢出的排查(时不时OOM+内存泄露+MAT排查)
(1)案例背景
首先说一下案例背景:线上有一个数据同步系统,是专门负责去同步别的一个系统的数据,别的一个系统会不停地发布自己的数据到Kafka中,然后该数据同步系统从Kafka里消费数据,接着保存到自己的数据库中。大概的流程如下图示:
结果就这么一个简单的系统,居然时不时就报一个内存溢出的错误。然后就得重启系统,过了一段时间又会再次内存溢出一下。而且当这个系统处理惩罚的数据量越来越大,它内存溢出的频率越来越高。到这种情况,就必须要处理惩罚一下了。
(2)堆内存OOM的三种原因
既然每次重启后过一段时间会出现内存溢出的问题,那么就说明每次重启后,内存都会不断上涨。
JVM出现内存溢出,通常有三种原因:
原因一是并发太高,大量并发创建过多的对象,导致系统直接瓦解了
原因二是内存走漏,有很多对象都在内存里,无论如何GC都接纳不掉
原因三是代码问题,导致某种情况下加载了大量数据,创建大量对象
那么这个场景是怎么回事呢?这个系统的负载并不高,虽然数据量不少,但并不是瞬时高并发场景。这么看来,很大概是随着时间推移,某种对象越来越多,都在内存里了。然后不断地触发GC,结果每次GC都接纳不掉这些对象。不停到最后,内存实在不足了,就会内存溢出。如下图示:
(3)通过jstat来确认我们的推断
接着直接在一次重启系统后,用jstat观察JVM运行的情况。我们发现,老年代的对象不停在增长,不停在增长。每次YGC过后,老年代的对象就会增长不少。而且当老年代的利用率达到100%后,会正常触发FGC。但是FGC根本接纳不掉任何对象,导致老年代利用率还是100%。然后老年代利用率维持100%一段时间过后,就会报内存溢出的问题。因为此时再有新的对象进入老年代,实在没有空间存放它了。
以是这就基本确认了我们的判定:每次系统启动,不知道什么对象会不停进入堆内存。而且随着YGC执行,对象会不停进入老年代。最后触发FGC都无法接纳老年代的对象,最终就是内存溢出。
(4)通过MAT找到占用内存最大的对象
在内存快照中,发现有一个队列数据布局,直接引用了大量的数据,正是这个队列数据布局占满了内存。
那这个队列是干什么用的?从Kafka取出的数据会先写入这个队列,接着再从这个队列写入数据库。由于要额外做一些中间的数据处理惩罚和转换,以是才在中间加一个队列。如下图示:
那么问题就出在这个队列是怎么用的,由于从Kafka消费数据是可以一下子消费一批的(比如几百条数据)。以是当时直接把每次消费的一批数据做成一个List,然后放入队列中。最后就造成了如下这种情况,这个队列有大量元素,每个元素都是一个List,每个List有几百条数据。
以是这种做法一定会导致内存中的队列积压大量数据,最终就会OOM。而且只要数据还停留在队列中,就没有办法被接纳。如下图示:
这就是一个典范的对生产和消费的速率没控制好的案例:从Kafka里消费出数据放入队列的速度很快。但从队列里消费数据举行处理惩罚,然后写入存储的速度较慢。最终导致内存队列快速积压数据,导致内存溢出,而且这种让队列里的每个元素都是一个List的做法,也会导致内存队列能容纳的数据量大幅膨胀。
最终的解决方法很简单:把上述内存队列的利用修改一下,做成定长的阻塞队列即可。然后每次从Kafka消费出数据,直接将数据写入队列,而不是List。
比如队列中最多1024个元素,那么内存中最多就是1024个数据,如下图示:
一旦内存队列满了,Kafka消费线程就会制止工作,因为被队列阻塞住了,就不会让内存队列中的数据过多。
8.线上系统的JVM参数优化、GC问题定位排查、OOM分析解决总结
(1)JVM运行系统时的工作原理
一.内存各个部门的划分
二.代码执行过程中各内存地区如何共同工作
三.对象是如何分配的
四.GC如何触发
五.GC执行的原理是什么
六.常见的用于控制JVM的一些核心参数
(2)JVM参数在上线前如何预设、测试时如何调整、上线后如何优化
对于一个开发好的系统:首先应该如何通过预估的方法给它设置一些相对合理的JVM参数,然后在测试时如何合理的优化调整JVM参数,接着在线上运行时如何对JVM举行监控,如何对线上出现GC性能问题时举行合理优化。
(3)如何分析定位解决线上OOM问题
出现OOM的三种原因,三种OOM的定位和解决方法,OOM的一些生产案例。
文章转载自:
东阳马生架构
原文链接:
JVM实战—13.OOM的生产案例 - 东阳马生架构 - 博客园
体验所在:
引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程计划器_表单引擎_工作流引擎_软件架构
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复
使用道具
举报
0 个回复
倒序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
回帖后跳转到最后一页
发新帖
回复
郭卫东
论坛元老
这个人很懒什么都没写!
楼主热帖
SQL的约束
界面组件DevExpress ASP.NET Core v21. ...
vue 详情页返回列表,过滤查询条件保留 ...
Cilium 系列-3-Cilium 的基本组件和重 ...
用python对美女内容采集,舞蹈区内容真 ...
mysql5.7.35安装配置教程【超级详细安 ...
2023团队天梯模拟赛 L2-3 智能护理中心 ...
ASP.NET Core MVC 从入门到精通之HttpC ...
JS基础二--字面量和变量
在本地运行Kusto服务器
标签云
AI
运维
CIO
存储
服务器
浏览过的版块
物联网
Oracle
网络安全
移动端开发
Mysql
快速回复
返回顶部
返回列表