Java虚拟机三:OutOfMemoryError异常分析
根据Java虚拟机规范,虚拟机内存中除过程序计数器之外的运行时数据区域都会发生OutOfMemoryError(OOM),本文将通过实际例子验证分析各个数据区域OOM的情况。为了更贴近生产,本次所有例子都是通过调用接口触发,并使用jvisualvm工具监控tomcat内存进行分析。
一、Java堆溢出
Java堆主要用于存储对象和数组实例,只要不断创建对象或者数组,并且保证CG Roots(垃圾收集器对象)到对象之间有可达路径来避免垃圾回收机制清除这些对象,在对象数量到达最大堆的限制后就会产生内存溢出异常。
java代码:
    static class OOMObject{};
    /**
     * 测试Java堆OOM
     */
    private void testHeapOom(){
        List<OOMObject> oomObjects=new ArrayList<>();
        int count=;
        boolean flag=true;
        while(flag){
            try{
                oomObjects.add(new OOMObject());
                count++;
            }catch (Throwable throwable){
                flag=false;
                System.out.println("count="+count);
            }
        }
    }
设置Jvm参数:在tomcat的 catalina.sh 中添加 JAVA_OPTS="$JAVA_OPTS -Xms64M -Xmx128M -XX:+HeapDumpOnOutOfMemoryError" 即初始堆内存为64M,最大堆内存为128M, -XX:+HeapDumpOnOutOfMemoryError 的含义是当虚拟机内存溢出后Dump出当前内存堆转储快照,这样可以方便事后分析。
重启tomcat后,设置jvisualvm连接,在JVM参数中可以看到刚才添加的参数,表示设置成功:
  
切换到监控界面,在“堆”监控界面,可以看到堆大小稳定在64M,64即为我们设置的初始堆大小:
  
接下来,调用接口触发Java堆OOM,同时监控tomcat日志以及“堆”监控界面。
tomcat日志:

tomcat日志中很清楚地给出提示: java.lang.OutOfMemoryError: Java heap space (内存溢出异常:Java堆空间)。
jvisualvm监控:

从jvisualvm监控可以看到,不断创建对象需要更多的对空间来存储对象,当使用的堆到达设置的最大值128M的时候,就触发了OOM。同时可以看到,在堆内存占用到达设置的最大堆内存之后,内存使用量又急剧下降,这是为什么呢?这是因为当发生 java.lang.OutOfMemoryError: Java heap space 之后,Java虚拟机会对整个Java堆进行Full GC,Full GC使得堆使用量急剧下降。
二、虚拟机栈和本地方法栈溢出
由于在HotSpot中不区分虚拟机栈和本地方法栈,所以对于HotSpot来说,设置 -Xoss (设置本地方法栈大小)是无效的,栈容量只由 -Xss 参数设置。栈溢出测试代码如下:
private int stackLength=1;
/**
* 测试栈溢出
*/
private void testStackLeak(){
try{
stackLength++;
testStackLeak();
}catch (Throwable e){
System.out.println("stackLength="+stackLength);
e.printStackTrace();
}
}
同时设置JVM参数: -Xss512k ,和第一步一样,调用接口后观察tomcat日志:

可以看到,tomcat日志打印出了 java.lang.StackOverflowError 的异常,并且栈的深度为2761。
接下来,将 -Xss 大小设置为1M,即设置JVM参数 -Xss1M ,调用接口后,再次观察tomcat日志:

可以看到,抛出 java.lang.StackOverflowError 异常的时候,栈的深度达到了7972。通过对比,我们可以很清楚地看到 -Xss 参数的作用:设置每个线程的栈大小,当线程请求的栈深度大于此设置的时候,就会出现 java.lang.StackOverflowError 异常。
三、方法区溢出
在HotSpot中,从jdk8开始,方法区的实现由永久代变更为元空间(MetaSpace),元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小,但也不是无限制的,可以配置参数来进行调整。现在通过代码来进行测试分析:
(1)新建一个类 TestMetaSpaceClass ,注意该类不包含包名:
public class TestMetaSpaceClass {
}
将该类编译后的class文件放到 /mnt/class 路径下;
(2)测试方法中不断加载该类:
        try {
            //准备url
            URL url = new File("/mnt/class").toURI().toURL();
            URL[] urls = {url};
            //获取有关类型加载的JMX接口
            ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
            //用于缓存类加载器
            List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
            while (true) {
                //加载类型并缓存类加载器实例
                ClassLoader classLoader = new URLClassLoader(urls);
                classLoaders.add(classLoader);
                classLoader.loadClass("TestMetaSpaceClass");
                //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
                System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
                System.out.println("active: " + loadingBean.getLoadedClassCount());
                System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
设置JVM参数 -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=32M ,调用接口后观察tomcat日志以及jvisualvm中Metaspace的变化曲线。
tomcat日志:

通过tomcat日志可以看到,抛出了 java.lang.OutOfMemoryError: Metaspace 的异常,此时总共加载的数目为5382
jvisualvm监控:

通过jvisualvm中Metaspace的监控也可以看到,随着类的加载,元空间使用量逐渐增加,当超过最大值32M的时候,发生了OOM,接下来,将JVM参数再调大一点,设置为 -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=48M ,再次调用接口测试:
tomcat日志:

jvisualvm监控:

从tomcat日志和jvisualvm监控可以看出,当把参数 -XX:MaxMetaspaceSize 调大以后,可以加载更多的类。
以上分析如有错误之处,还请各位指正,谢谢!
Java虚拟机三:OutOfMemoryError异常分析的更多相关文章
- Java虚拟机内存溢出异常--《深入理解Java虚拟机》学习笔记及个人理解(三)
		
Java虚拟机内存溢出异常--<深入理解Java虚拟机>学习笔记及个人理解(三) 书上P39 1. 堆内存溢出 不断地创建对象, 而且保证创建的这些对象不会被回收即可(让GC Root可达 ...
 - Java虚拟机类加载机制——案例分析
		
转载: Java虚拟机类加载机制--案例分析 在<Java虚拟机类加载机制>一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的最后留了一个悬念给各位,这里来揭开这个悬 ...
 - JVM:Java常见内存溢出异常分析
		
转载自:http://www.importnew.com/14604.html Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆 ...
 - 深入理解java虚拟机(三)-----类加载机制
		
类加载机制jvm把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被jvm直接使用的java类型.在java中,类型的加载.连接和初始化都是在程序运行期间完成的 ...
 - 深入理解Java虚拟机(三)——垃圾回收策略
		
所谓垃圾收集器的作用就是回收内存空间中不需要了的内容,需要解决的问题是回收哪些数据,什么时候回收,怎么回收. Java虚拟机的内存分为五个部分:程序计数器.虚拟机栈.本地方法栈.堆和方法区. 其中程序 ...
 - Java虚拟机三 Java堆和栈
		
Java堆是和Java应用程序关系最为紧密的内存空间,几乎所有的对象都存放在堆中.并且堆是完全自动化管理的. 根据垃圾回收机制的不同,Java堆有可能有不同的结构.最为常见的一种就是将整个Java堆分 ...
 - 虚拟机性能监控与故障处理工具(深入理解java虚拟机三)
		
JDK自带的工具可以方便的帮助我们处理一些问题,包括查看JVM参数,分析内存变化,查看内存区域,查看线程等信息. 我们熟悉的有java.exe,javac.exe,javap.exe(偶尔用),jps ...
 - java虚拟机(十一)--GC日志分析
		
GC相关:java虚拟机(六)--垃圾收集器和内存分配策略 java虚拟机(五)--垃圾回收机制GC 打印日志相关参数: -XX:+PrintGCDetails -XX:PrintGCTimestam ...
 - Java常见内存溢出异常分析(OutOfMemoryError)
		
原文转载自:http://my.oschina.net/sunchp/blog/369412 1.背景知识 1).JVM体系结构 2).JVM运行时数据区 JVM内存结构的相关可以参考: http:/ ...
 
随机推荐
- 对Python这门课程的理解。
			
这门课程是现在热门,对之后的就业和利用的帮助还是很大的. 希望能学完整本书并且能学以致用,而不是单单只获得理论知识. 学完之后能用于数据库.大数据处理.图形编程等等
 - SSH项目的pom.xml文件
			
<!-- 属性 --> <properties> <spring.version>4.2.4.RELEASE</spring.version> < ...
 - Spring MVC 的 XML 配置方式
			
索引: 开源Spring解决方案--lm.solution 参看代码 GitHub: solution/pom.xml solution/webapi/pom.xml solution/mapper/ ...
 - Java线程池中submit() 和 execute()方法的区别
			
两个方法都可以向线程池提交任务, execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorS ...
 - SSM-SpringMVC-33:SpringMVC中拦截器Interceptor讲解
			
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 拦截器Interceptor: 对处理方法进行双向的拦截,可以对其做日志记录等 我选择的是实现Handler ...
 - Flask开发微电影网站(一)
			
1.用到的Flask知识 1.使用整形,浮点型,路径型,字符串型下正则表达式路由转化器 2.使用GET与POST请求,上传文件,cookie获取与响应,404处理 3.使用模板自动转义,定义过滤器,定 ...
 - Java生成名片式的二维码源码分享
			
世界上25%的人都有拖延症——但我觉得这统计肯定少了,至少我就是一名拖延症患者.一直想把“Java生成名片式(带有背景图片.用户网络头像.用户昵称)的二维码”这篇博客分享出来,但一直拖啊拖,拖到现在, ...
 - Dubbo源码-Dubbo是如何随心所欲自定义XML标签的
			
叨叨 今天考虑了很久要不要写这篇文章. 距离<Dubbo源码>系列的开篇到现在已经快两个月时间了.当时是想着工作上的RPC框架使用存在一些让人头疼的问题,就来看看Dubbo给出了一套什么样 ...
 - 2013-09-16 构建C1000K的服务器(1) – 基础
			
http://www.ideawu.net/blog/archives/740.html 著名的 C10K 问题提出的时候, 正是 2001 年, 到如今 12 年后的 2013 年, C10K 已经 ...
 - Redis Rpop 命令
			
Redis Rpop 命令用于移除并返回列表的最后一个元素. 语法 redis Rpop 命令基本语法如下: redis 127.0.0.1:6379> RPOP KEY_NAME 可用版本 & ...