JVM的内存溢出问题,是个常见而有时候有非常难以定位的问题。定位内存溢出问题常见方法有很多,但是其实很多情况下可供你选择的有效手段非常有限。很多方法在一些实际场景下没有实用价值。这里总结下我的一些定位思路。
要定位JVM内存溢出问题,首先要对JVM的内存布局有一定的了解,对常见的JVM内存工具要比较熟悉。所谓工欲善其事,必先利其器。而熟悉JVM的内存管理机制是你定位JVM内存问题的基石。首先介绍下JVM的内存管理机制:
JAVA程序和C类程序一个重要的区别就是JAVA中的内存回收管理工作有JVM完成,而不需要为程序中的每个new/malloc去delete/free。那JVM这么智能怎么还有内存溢出问题呢,一个常见的原因是我们的JAVA程序中长时间的持有了不该持有的对象,或者申请了过多的对象使得JVM的内存不够用。
JVM管理的几个内存区域包括以下几个内存区域:
1、 方法区:用于存储JAVA类信息、常量、静态变量。这个区域也可以发生垃圾回收,比如当一些类不在被引用时JVM可以卸载这个类,不过这种回收动作很少发生。另外所有线程都共享方法区,因此线程对方法区的访问被设计为线程安全的。
2、 虚拟机栈:JAVA虚拟机栈是线程私有的,每当启动一个新线程时,JVM都会为它分配一个JAVA虚拟机栈。没当线程调用方法时,JVM都会为虚拟机栈压入一个栈帧,该栈帧用于存储参数、局部变量、中间运算结果、方法出口等。
3、 本地方法栈:和虚拟机栈类似,只是他是专为JAVA中的Native方法服务。当线程进入本地方法后,它已经脱离的JVM的限制,甚至可以直接使用本地处理器中的寄存器。实际上本地方法的调用机制非常依赖于JVM的具体实现。
4、 堆:由JVM启动时创建,由所有线程共享,用于存放对象的实例。一般情况下它是JVM中管理的内存中最大的一块。绝大部分JVM内存问题都发生在这一块。
5、 程序计数器:同虚拟机栈一样,它也是线程私有,启动线程是创建。程序计数器的中保存的内容总是下一条将被执行的指令的地址。
这几个内存区域,除了程序计数器区域外,其他几个区域都有可能发生内存溢出问题。常见的内存溢出有两种:
1、方法区溢出。出现该问题时,JVM会报如下类似错误:java.lang.OutOfMemoryError : PermGenSpace ,Perm区的最大内存大小可以通过-XX:MaxPermSize=指定。引起这类内存溢出原因一般有两个,一个是常量池太大,一个是需要加载的CLASS类太多。出现问题的时候排查下这两种可能性,问题可以很快找到。这类问题程序稳定后也很少出现。
2、堆内存溢出。在JVM可使用的最大堆内存可以在启动的时候通过-Xmx参数指定。堆内存溢出是最为常见的内存溢出问题,发生堆内存溢出时,JVM会报告如下错误:java.lang.OutOfMemoryError : java heap space。
这里列举下在定位堆内存溢出时,常见的方法和思路。堆内存溢出顾名思义就是,堆内存不够用了,如果程序设计的最大对内存已经耗尽,那说明程序设计存在问题,不该申请很多内存的逻辑申请了很多的内存,该释放的对象没有释放。最重要的问题是就要要找出到底是什么对象没有及时释放,导致占用了过多的内存。常见的方法:
a、 一个强大的定位工具是使用 jprofiler,通过jprofiler可以实时的监控到,当前的堆内存的总体使用情况及当前存活的对象、大小、分配树、对象引用链等等,功能非常全面。如下图所示:
通过该工具还可以实时的生成堆内存快照,然后通过不同时间点生成的内存快照做比较已确定内存增长点。但是实际使用中如果程序征程运行中占用的内存就比较高,比如800M左右,而在32位机器上,JVM可以使用的最高内存在1280M左右,如果应用程序设置的最大堆内存是1024M,那么实际即将发生堆内存溢出时,程序使用的内存是处一个比较高的位置。这时候通过jprofiler监控效果就很不理想,我在windows server 2003 32位服务器上做测试,想通过jprofiler监控一个TOMCAT应用程序,该应用程序常态中占用内存在800M左右,启用jprofiler监控后,jprofiler监控程序本身就会变得很不稳定,常常莫名挂死(通过jprofiler监控应用程序的时候实际上上jprofiler对应用程序又做了一层包装后启动的,因此jprofiler监控程序挂死,等于被监控的程序挂死)。很难抓取到有用的信息。
因此jprofiler在32位机器上只适用用小内存应用程序。
b、 使用JVM自带的jmap工具导出堆信息分析,这个工具可以通过如下命令到处自定的JVM进程的堆内存:jmap –dump:format=b,file=heap.bin <pid> 。
但这个工具也有一样的问题,在应用程序使用的内存处于高位时,使用jmap导出堆信息会出现空间不足,无法导出的问题。Java自带的很多工具都有类似的限制,所以实际上你的选择并不多。
c、 在启动JVM的时候加上以下两个参数:
-XX:HeapDumpPath=./dumpfile.hprof
-XX:+HeapDumpOnOutOfMemoryError
表示出现OOM错误后,将堆信息dump出来。不过这个参数也有个问题,在实际使用的时候发现在windows平台上,如果程序是以TOMCAT服务的方式运行,出现OOM错误后堆信息也dump不出来。
d、 通过visualVM程序监控JVM,JDK 1.6中自带的可视化监控工具,这个工具比较实用,功能也比较强大。可以监控线程信息,堆内存信息,还可以实时导出堆信息以及强制GC。监控本地JVM直接启动该工具后就可以监控,远程监控需要启动jstatd和jrxml。
启动jstatd方法:新建jstatd.all.policy文件,添加如下内容,tools.jar包的路径根据实际情况修改:
grant codebase "file:/home/ndmc/tomcat/jdk/jre/lib/tools.jar" {
permission java.security.AllPermission;
};
执行启动命令./jstatd -J-Djava.security.policy=jstatd.all.policy,默认端口为1099,后面可跟-p指定其他端口号
使用jrxml远程监控JVM的时候需要加上以下参数:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8849
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=hostip
监控效果如下:
在监控过程中可以手动的GC和DUMP堆内存,还可以设置Heap dump on OOME: enabled,使得在堆内存溢出的时候可以dump heap。不过根据实际使用,建议最高在堆内存即将溢出的时候就应该手动dump heap,因为等到OOM的时候常常程序已经挂死,heap已经导不出来了。
e、 导出dump信息后就可以通过jprofiler工具或者HeapAnalyzer做分析。这两个工具都很强大,网上有很多的使用指导。可以初步分析出到底是什么对象在占用内存,以及相应的引用链,到这一步在结合源代码在定位内存溢出问题就相对容易了。
总结:定位内存溢出问题需要一个冷静的头脑、敏锐的观察能力、缜密的分析问题能力。甚至常常需要根据一些现象做猜测然后验证,找出最后的元凶,有可能内存的溢出是有多个方面引起的:代码BUG、软件设计问题、网络的吞吐量以及网路连接问题、数据库问题等都可能是相关需要排查的因素,甚至有时候可能是操作系统或者JVM本身存在的BUG导致。而能够在JVM堆内存即将溢出的时候导出堆信息是问题定位的关键,只要能够导出堆信息,一系列的猜测、分析是否正确就有可靠的证据。
---------------------
原文:https://blog.csdn.net/xishanxinyue/article/details/15336551
来源:oschina
链接:https://my.oschina.net/u/4389837/blog/3633086