帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
JVM:全面理解线上服务器内存溢出(OOM)问题处理方案(一)
发布时间:2024-03-06 08:38:10   分类:帮助文档
JVM:全面理解线上服务器内存溢出(OOM)问题处理方案(一) 0. 引言 前段时间生产上遇到了OOM问题,导致服务出现了短时间的不可用,还好处理及时,否则也将酿成大祸。OOM问题也是生产中比较重要的问题,所以本期我们针对OOM问题特别讲解,结合理论与实际案例来带大家彻底攻克OOM问题处理。 1. OOM问题产生的原因 1.1 JVM内存布局/内存模型/运行时数据区域 要解决问题,我们首先要清楚问题产生的原因。 OOM(Out Of Memory),即内存溢出,其问题表示java虚拟机在运行过程中,所占用的内存超过限制的内存大小了,导致没有多余的内存继续运行 我们要弄清楚该问题,首先要先了解java程序运行时的内存布局,我们知道java程序是运行在JVM(java虚拟机)之上的。因此其运行时的内存布局也就是JVM的内存布局。 JVM的内存布局(运行时数据区域)一共分为5部分: 堆:用于存放程序运行时创建的对象或数组,是我们最常操作的内存区域。栈:用于存放栈帧,每个方法都会创建自己的栈帧,栈帧中包括局部变量表、操作栈、动态链接、返回地址等信息,其中局部变量表里存放基本数据类型和堆中对象的引用程序计数器:用来存放在一条指令所处的位置的,这里是不会发生内存溢出的,因此大家了解即可本地方法栈:与栈的作用类似,只不过栈用于管理JVM方法的调用,而本地方法栈用于管理本地方法的调用,所谓本地方法就是底层的操作系统指令、C、C++方法等方法区:每个线程共享的内存区域,用于存储已经被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 1.2 常见OOM原因 我们解决OOM问题,一定要先理解JVM内存模型,如上所示,本地方法栈管理的是本地方法,相对固定,引起OOM的概率较低,而方法区存储类型信息、常量、静态变量、代码缓存之类的数据,能够出问题的无非在于静态变量数据是否过大、引入加载的class是否过多,这类问题发生的概率也比较小。计数器本身就是计数的作用,更不可能发生OOM 所以在运行时能产生OOM问题的基本就是堆和栈了,其中在实际运行中最常见的就是堆内存溢出,我们这次产生的问题也是堆内存溢出导致,堆内存溢出原因主要是以下几种: (1)创建了一个超大对象,比较常见的是一个大数组,大集合 (2)对象引用没有释放,导致垃圾无法回收,产生内存泄漏,从而导致可用内存减少 (3)突然而来的高并发,导致流量飙升,资源占用迅速提升,服务器配置无法跟上实际使用 (4)重写finalize引发频繁GC,这个问题的典型案例是小米云的C++程序员重写finalize导致了线上OOM。在java里很少见 栈存储的是基础数据类型和堆对象的引用,这些数据理论上占用并不多,要达到内存溢出,那就是不断的叠加导致的这些数据暴增,《深入理解JAVA虚拟机》中给出的栈溢出的原因是线程请求的栈深度大于虚拟机允许的深度了,所谓的栈深度就是方法嵌套调用的次数,所以说的直白点就是嵌套循环调用次数太多,即考虑如下原因: (1)是否有递归调用 (2)是否有大量循环或死循环 如何定位是堆内存溢出还是栈内存溢出? 在java中出现堆内存溢出,可以在报错异常java.lang.OutOfMemoryError后明显看到"java heap space"的提示,即堆空间;而栈溢出可以看到StackOverflowError错误,一般这类错误在我们开发测试时就能暴露出来,所以通过报错内容的文字描述即可知道溢出位置 1.3 其他导致OOM问题的原因及解决之法 除了上述说的堆栈导致的OOM问题,其实还有其他内存区域会导致OOM问题,只是相对来说更加少见,或者基本都能在开发测试中暴露出来,很少出现在线上环境,为了让大家有个全面的了解,我们也梳理出来 首先就方法区而言,上面已经讲解到,其原因就是: (1)静态变量过大,这个原因实际上概率很小,因为创建的静态变量一般在开发时就会把控,基本不会太大。 (2)class加载多大,比如这个项目引入了超多的jar包,编译出超多的class文件,就会导致此类问题,这类问题的解决可以通过精简项目解决,如果实在都精简不了,因为class文件在jdk1.8之前是存储在永久代的,所以可以调大永久代空间,在JVM启动脚本添加如下参数:-XX:MaxPermSize,在jdk1.8之后使用元空间替换了永久代,所以可以调整如下参数:-XX:MaxMetaspaceSize (3)class加载了多次,这种一般是启动异常,再重启下项目即可,如果重启完问题仍未解决考虑其他原因 其次就是本地方法栈出现OOM问题: 本地方法栈出现OOM问题,一般会报错Unable to create new native thread,即无法创建新本地方法线程。这个出现的原因是: (1)本地方法线程数超过了最大线程数限制(操作系统最大线程数ulimit和内核线程数kernel.pid_max) (2)本地方法栈内存不足 而这两个原因的解决一般也就是三个思路: (1)增加机器配置 (2)堆栈内存占用过多,导致本地方法栈内存占用变小,将堆栈内存调小,或者排查堆栈占用是否异常,是否有偏高的情况 (3)程序中是否有线程未正常回收,导致线程数占用,这点排查可以根据下文讲解的排查思路进行。 2. 解决OOM问题思路 知道了OOM问题的原因,那么我就来看解决问题的思路,问题的具体案例多种多样,但是核心思路是不变的。 针对于栈内存溢出:一般通过日志找到报错的代码位置,针对性排查是否有循环调用、死循环的问题即可, 针对堆内存溢出: (1)如果是调用量激增导致的内存不足,那么考虑增加机器或拓展内存资源 (2)机器资源充足,只是JVM分配的内存较小,考虑调大JVM内存,通过参数最大内存-Xmx 和最小内存-Xms (2)如果是超大对象,那么考虑业务场景,是否需要查询如此大的对象,考虑分步查出 (3)如果是对象引用没有释放,就排查代码逻辑,查看是否有没有正常释放对象引用的地方,比如ThreadLocal没有正常remove,导致对象一致引用。 实际上大部分开发在掌握了详细的报错信息后,都能定位对应的代码位置,来排查到错误,而OOM问题让很多同学望而生畏的原因是,不知道怎么定位报错,不知道怎么查看报错信息。 下面我们就来讲解如何定位报错信息 3. 定位OOM报错 1、首先找到是哪个服务有问题,一般大型系统会部署预警提醒,根据预警信息找到对应服务器即可,小型系统没有部署预警的直接到服务器上逐一查看。如果你说你们是大型系统,又没有部署预警提醒的,那怎么排查? 方法根据反馈信息,一台一台服务器查吧,事后赶紧让运维把预警系统部署上。 2、定位到服务器后,到对应服务器上通过top指令,查看是哪个进程占用内存资源较高 这里为了模拟操作,我写了一个程序用来产生OOM问题,同时为了尽快模拟出OOM,我们将堆内存限定为200M,想要更快显示效果你可以限定的更小 java -jar -Xms200M -Xmx200M cpu_oom_demo-0.0.1-SNAPSHOT.jar top指令可以看到内存占用情况,因为这里我限定了200M,如果线上真实情况,可能会打到80%+ 同时在日志中也会报错堆内存溢出 如果定位到是java进程导致,可以通过jps -l指令来查看所有正在运行的java进程 jps支持的参数 jps -q:查看所有运行的Java进程,但只显示进程号pid jps -m:只显示传递给main方法的参数 jps -l:只显示运行程序主类的包名,或者运行程序jar包的完整路径 jps -v:单独显示JVM启动时,显式指定的参数 jps -V:显示主类名或者jar包名。 3、然后通过top -Hp 进程pid指令来查看是哪个线程占用的内存资源高,如果自定义了线程名了,可以通过此处打印的线程名就能定位到具体是哪块功能线程引起的问题,从而定位问题(这也是阿里java规范中要求自定义线程池,自定义线程名的重要性) 上述看到java程序的pid是1826,那么执行top -Hp 1826 看到从线程无法定位到业务功能 4、如果没有自定义线程名,或者根据线程名也看不出具体原因,那么就需要导出堆日志了,通过jmap指令导出堆日志,因为该指令执行期间会导致业务线程无法运行,所以在导出前我们要确保有其他节点顶着,同时将该节点从注册中心/负载均衡中下架(注意不能关闭服务,关闭后JVM日志就导不出来了),然后执行jmap 我们先导出进程中占用内存空间最大的前20的对象名,1826为进程ID jmap -histo 1826 | head -20 可以看到是User对象的占用过高导致,这里如果这个对象你能够定位到具体的使用位置,或者说这个对象使用的地方并不多,那么次数就能根据这个信息定位到问题。比如这里是用户实体类占用过高,那理所当然考虑是不是对用户信息的访问高飙升,或者用户信息访问后没有正常释放,通过这些信息就可以进行辅助排查。但如果你调用这个对象的地方很多,那还不足以定位问题,我们还要进一步进行排查 5、这里如果服务是正常的调用,并且服务器资源还算可以,比如我这里出现了OOM,但实际服务器内存占用才22%。那么我们可以通过jps -lv将java进程中设置的JVM参数打印出来 可以看到这里因为设置了JVM内存为200M,如果资源允许将该内存调大即可。注意这里显示的是JVM启动时,显式指定的参数,如果没有查询到说明使用的都是JVM默认参数值。但如果调大后仍然有OOM问题并且理论调用量也不高,或者在承受范围的,那就是对象没有正常回收导致的了,我们继续排查 如果想要查询更多JVM运行时参数,可以通过jnifo指令 jinfo [option1] 进程pid 其中[option1]可选项如下: :第一个参数不写,默认输出JVM的全部参数和系统属性。 -flag :输出与指定名称对应的所有参数,以及参数值。 -flag [+|-]:开启或者关闭与指定名称对应的参数。 -flag =:设置与指定名称对应参数的值。 -flags:输出JVM全部的参数。 -sysprops:输出JVM全部的系统属性。 jinfo -flags 2019 6、如果根据对象无法排查问题,我们还要进一步打印堆日志,堆日志导出指令 jmap -dump:format=b,file=文件名 进程pid # eg 这里演示使用的自定义文件后缀,不影响分析,但建议按照规范使用.hprof jmap -dump:format=b,file=/usr/local/oom.dump 1826 当然上述方法属于事后处理了,如果想要出现OOM问题时自动生成dump文件,可以在配置中开启 -XX:+HeapDumpOnOutOfMemoryError 参数表示当JVM发生OOM时,自动生成DUMP文件。 -XX:HeapDumpPath=${目录}参数表示生成DUMP文件的路径,也可以指定文件名称,例如:-XX:HeapDumpPath=${目录}/java_heapdump.hprof。如果不指定文件名,默认会在项目根目录下生成一个文件,文件名格式为:java___
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价