这篇文章,我们来讲一下JVM中最常见,也是最严重的性能问题OOM。
OOM是OutOfMemoryError的英文缩写,通俗的讲,就是内存溢出错误。上次,我们说到JVM中有GC资源回收机制,会对新生代、老年代、元空间这些内存比较大的区域进行回收,提高内存的复用率,为什么还会出现内存溢出错误呢?
说到内存溢出,我们得先说它的孪生兄弟内存泄露。JAVA程序在启动时,从内存条中申请了一段内存空间,程序在运行过程中,就把新对象丢到这段内存空间中,然后,使用GC资源回收策略,释放被占用的内存空间,这是理想中的状态。但是,在实际项目中,经常会出现一些意外情况,如某一对象进入JVM的内存空间后,生命周期特别长,长到成为了‘老赖’,虽然有GC资源回收,但是,对于老赖也无能为力,回收不了。如果,这样的‘老赖’很少,还无所谓,但是,一旦多了起来,就导致,剩余可用的内存空间越来越少,这样一种现象,就称为‘内存泄露’。如果此时程序运行过程中,有一个比较大的对象,需要一个比较大的内存空间来存放,此时,剩余的内存不足够存放这个大对象,这时,就内存溢出就出现了。
所以,内存溢出,
这些,是从概念的理解上,就能推断出来的可能原因,当然,实际项目,可能性会更多,但是,我们把这些先搞定,那OOM的问题就不是大问题了。
这个错误,相信大家都不陌生,错误信息中,明确提示了java堆空间。堆空间不够用了。可能是程序运行过程中,申请了很多对象放在内存中,这些对象又都还‘活’着,不能被GC回收,导致剩余可用堆空间不够用。或者JVM配置的堆太小,程序运行过程中,堆空间不够用。
出现这个错误,其原因是多次GC的效率非常低。内存空间不够用,通过GC来回收空间,但是,多次回收的效率非常低,几乎没有效果,所以,就报了这个错误,出现这个错误,一般是堆空间配置太小。
这个错误信息比较明确,数组对象大小超过了VM的限制,也就是说新对象太大。对于这个问题,先确认下是否是代码bug,真有必要申请这么大的对象吗?如果确认要申请大对象,那就调大JVM的堆空间大小。
提示信息中明确说了,是元空间。如果JDK低于1.8,提示的就是Permgen space永久代。永久代元空间里面存放的是类信息和常量池。一般来说,这些对象都不会特别大,所以,这个错误,比较少见。出现这个错误,我们可以调大JVM的MaxPermSize的值(JDK<1.8)或MaxMeteSpaceSize的值(JDK≥1.8)。
这个错误,字面意思很好理解,就是不能再创建活跃的线程了,但是,涉及的知识面却很广。
首先,任何一个程序能创建的进程和线程的总数量,受操作系统参数限制,这个可以查看程序启动后,内存文件limit,在这个文件中,可以看到线程值,如果此时程序参数都进程加线程的总数达到了这个限制值,就会出现上面的错误,如果是这个限制参数的原因,可以临时调大系统限制参数值,然后重启服务解决,当然,这种概率比较低,一般只会在高并发业务中才可能出现;
然后,我们还得知道,进程是资源的拥有者,线程只是使用资源,但是,每个线程都有自己的线程栈,这个线程栈在JVM中是有限制的,超了也会报上面的错误,如果是这样,一般还会伴随StackOverflowError错误,这个就需要调整-Xss线程栈的值。
Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory MApped File)实现高速 IO,但是它默认只有64MB,出现上面错误,可以调大-XX:MaxDirectMemorySize的大小,然后,去掉-XX:DisableExplicitGC配置
哈哈,这个错误要是被你遇到了。那我可得恭喜你,准备停机接受检查吧!这个错误什么意思呢,就是说一直在申请内存空间,把交换分区的空间都用光了。啥意思?咋来了个交换分区呢?交换分区,它是磁盘虚拟出来的,用来临时转存内存空间数据的,当内存不够用到时候,才会把内存中的数据转存到交换分区,以腾出内存空间。你现在不但内存空间不够用,交换分区空间都不够用了,哪还有内存啊,机器随时都可能‘嗝屁’。这种问题,要停掉一些其他程序,然后,仔细分析项目的堆栈信息,定位问题并解决。
首先,我们要明白,OOM是程序内存问题,出现这样的错误,很多时候,已经导致程序没有足够的内存去打印日志,所以,程序日志中不一定会有OOM的错误信息。但是,在遇到OOM问题时,我们需要OOM的错误信息,才能判断问题所在,所以,往往需要借助一些外部工具。
在JVM的配置参数中添加-XX:+
HeapDumpOnOutOfMemoryError,添加这个参数,JVM一旦出现OOM问题,就会打印出错误信息,但是,这个参数一般在开发和测试时才用,请不要用于生产环境。
JDK是java开发工具包集,它自带了jmap工具,使用jmap -dump:format=b,file=oom.bin pid的命令格式,可以dump下OOM信息。这是比较经典的做法,但是,如果堆栈比较大时,这个命令执行时间可能会非常长。
arthas是阿里开源和维护的一款java分析工具集,是java程序性能分析的高效工具。使用这个工具,直接使用heapdump命令,就能方便快速的dump下OOM信息,无论堆栈多大,dump的速度都非常快。
如果你的云服务器是阿里的,可以付费使用ARMS监控系统,这是一整套监控系统,通过这个监控系统,可以观察到OOM信息。
获取到了OOM信息之后,一般会使用MAT工具进行分析。如果你有java基础,你可以自己下载MAT工具进行分析,如果没有,也可以把dump下的OOM信息给开发人员进行分析。