文档章节

OutOfMemoryError异常

浮躁的码农
 浮躁的码农
发布于 2015/08/27 13:29
字数 1776
阅读 36
收藏 0
点赞 0
评论 0

在VM运行时数据区域中,除了程序计数器,其他在VM Spec中都描述了产生OutOfMemoryError(下称OOM)的情形,那我们就实战模拟一下,通过几段简单的代码,令对应的区域产生OOM异常以便加深认识,同时初步介绍一些与内存相关的虚拟机参数。下文的代码都是基于Sun Hotspot虚拟机1.6版的实现,对于不同公司的不同版本的虚拟机,参数与程序运行结果可能结果会有所差别。

1.Java堆

Java堆存放的是对象实例,因此只要不断建立对象,并且保证GC Roots到对象之间有可达路径即可产生OOM异常。测试中限制Java堆大小为20M,不可扩展,通过参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现OOM异常的时候Dump出内存映像以便分析。(关于Dump映像文件分析方面的内容,可参见《JVM内存管理:深入JVM内存异常分析与调优》。)

Java堆OOM测试

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
publicclassHeapOOM
{
    staticclassOOMObject
    {
    }
 
    publicstaticvoidmain( String[] args )
    {
        java.util.List<OOMObject> list =newArrayList<OOMObject>();
        while(true)
        {
            list.add(newOOMObject() );
        }
    }
}

运行结果:

?
1
2
3
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid7372.hprof ...
Heap dump file created [24724537bytes in0.699secs]
2.VM栈和本地方法栈

Hotspot虚拟机并不区分VM栈和本地方法栈,因此-Xoss参数实际上是无效的,栈容量只由-Xss参数设定。关于VM栈和本地方法栈在VM Spec描述了两种异常:StackOverflowError与OutOfMemoryError,当栈空间无法继续分配分配时,到底是内存太小还是栈太大其实某种意义上是对同一件事情的两种描述而已,在笔者的实验中,对于单线程应用尝试下面3种方法均无法让虚拟机产生OOM,全部尝试结果都是获得SOF异常。

1.使用-Xss参数削减栈内存容量。结果:抛出SOF异常时的堆栈深度相应缩小。
2.定义大量的本地变量,增大此方法对应帧的长度。结果:抛出SOF异常时的堆栈深度相应缩小。 
3.创建几个定义很多本地变量的复杂对象,打开逃逸分析和标量替换选项,使得JIT编译器允许对象拆分后在栈中分配。结果:实际效果同第二点

清单2:VM栈和本地方法栈OOM测试(仅作为第1点测试程序)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * VM Args:-Xss128k
 *
 */
publicclassJavaVMStackSOF
{
    privateintstackLength =1;
 
    publicvoidstackLeak()
    {
        stackLength++;
        stackLeak();
    }
 
    publicstaticvoidmain( String[] args )throwsThrowable
    {
        JavaVMStackSOF oom =newJavaVMStackSOF();
        try
        {
            oom.stackLeak();
        }
        catch( Throwable e )
        {
            System.out.println("stack length:"+ oom.stackLength );
            throwe;
        }
    }
}
运行结果
?
1
2
3
4
stack length:3155
Exception in thread"main"java.lang.StackOverflowError
    at memoryManagement.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
    at memoryManagement.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
如果在多线程环境下,不断建立线程倒是可以产生OOM异常,但是基本上这个异常和VM栈空间够不够没有直接关系,甚至是给每个线程的VM栈分配的内存越多反而越容易产生这个OOM异常。

原因其实很好理解,操作系统分配给每个进程的内存是有限制的,譬如32位Windows限制为2G,Java堆和方法区的大小JVM有参数可以限制最大值,那剩余的内存为2G(操作系统限制)-Xmx(最大堆)-MaxPermSize(最大方法区),程序计数器消耗内存很小,可以忽略掉,那虚拟机进程本身耗费的内存不计算的话,剩下的内存就供每一个线程的VM栈和本地方法栈瓜分了,那自然每个线程中VM栈分配内存越多,就越容易把剩下的内存耗尽。 

清单3:创建线程导致OOM异常

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * VM Args:-Xss2M (这时候不妨设大些)
 */
publicclassJavaVMStackOOM
{
    privatevoiddontStop()
    {
        while(true)
        {
        }
    }
 
    publicvoidstackLeakByThread()
    {
        while(true)
        {
            Thread thread =newThread(newRunnable()
            {
                @Override
                publicvoidrun()
                {
                    dontStop();
                }
            } );
            thread.start();
        }
    }
 
    publicstaticvoidmain( String[] args )throwsThrowable
    {
        JavaVMStackOOM oom =newJavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

运行结果

?
1
<span></span>Exception in thread"main"java.lang.OutOfMemoryError: unable to createnewnativethread

特别提示一下,如果读者要运行上面这段代码,记得要存盘当前工作,上述代码执行时有很大令操作系统卡死的风险。

3.运行时常量池

要在常量池里添加内容,最简单的就是使用String.intern()这个Native方法。由于常量池分配在方法区内,我们只需要通过-XX:PermSize和-XX:MaxPermSize限制方法区大小即可限制常量池容量。实现代码如下

清单4:运行时常量池导致的OOM异常

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 *
 */
publicclassRuntimeConstantPoolOOM
{
    publicstaticvoidmain( String[] args )
    {
        // 使用List保持着常量池引用,压制Full GC回收常量池行为              
        List<String> list =newArrayList<String>();
        // 10M的PermSize在integer范围内足够产生OOM了     
        inti =0;
        while(true)
        {
            list.add( String.valueOf( i++ ).intern() );
        }
    }
}
运行结果
?
1
2
3
Exception in thread"main"java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(Unknown Source)

4.方法区

方法区用于存放Class相关信息,所以这个区域的测试我们借助CGLib直接操作字节码动态生成大量的Class,值得注意的是,这里我们这个例子中模拟的场景其实经常会在实际应用中出现:当前很多主流框架,如Spring、Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的Class可以加载入内存。

清单5:借助CGLib使得方法区出现OOM异常 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 *
 */
publicclassJavaMethodAreamOOM
{
    publicstaticvoidmain( String[] args )
    {
        while(true)
        {
            Enhancer enhancer =newEnhancer();
            enhancer.setSuperclass( OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(newMethodInterceptor()
            {
                publicObject intercept( Object obj, Method method, Object[] args, MethodProxy proxy )throwsThrowable
                {
                    returnproxy.invokeSuper( obj, args );
                }
            } );
            enhancer.create();
        }
    }
 
    staticclassOOMObject
    {
    }
}


运行结果

?
1
2
3
4
5
Caused by: java.lang.OutOfMemoryError: PermGen space       
       at java.lang.ClassLoader.defineClass1(Native Method)       
       at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)       
       at java.lang.ClassLoader.defineClass(ClassLoader.java:616)       
       ...8more

5. 本机直接内存

DirectMemory容量可通过-XX:MaxDirectMemorySize指定,不指定的话默认与Java堆(-Xmx指定)一样,下文代码越过了DirectByteBuffer,直接通过反射获取Unsafe实例进行内存分配(Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例,也就是基本上只有rt.jar里面的类的才能使用),因为DirectByteBuffer也会抛OOM异常,但抛出异常时实际上并没有真正向操作系统申请分配内存,而是通过计算得知无法分配既会抛出,真正申请分配的方法是unsafe.allocateMemory()

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 *
 */
publicclassDirectMemoryOOM
{
        privatestaticfinalint_1MB =1024*1024;
 
        publicstaticvoidmain( String[] args )throwsException
        {
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            Unsafe unsafe = ( Unsafe ) unsafeField.get(null);
            while(true)
            {
                unsafe.allocateMemory( _1MB );
            }
        }
}
运行结果
?
1
2
3
Exception in thread"main"java.lang.OutOfMemoryError      
 at sun.misc.Unsafe.allocateMemory(Native Method)      
 at org.fenixsoft.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

© 著作权归作者所有

共有 人打赏支持
浮躁的码农

浮躁的码农

粉丝 57
博文 605
码字总数 141390
作品 0
松江
程序员
《深入理解Java虚拟机》学习小记一之自动内存管理机制(一)

Java内存区域与内存溢出异常 一、概要 我们可以带着以下几个问题去学习自动内存管理机制,罗列如下: 什么操作可能导致内存溢出? 有哪些种类的内存溢出? 都是在内存的哪些区域溢出? 垃圾收...

felixlv ⋅ 2013/05/07 ⋅ 0

jvm调优经验分享

当Java程序申请内存,超出VM可分配内纯的时候,VM首先可能会GC,如果GC完还是不够,或者申请的直接超够VM可能有的,就会抛出内 存溢出异常。从VM规范中我们可以得到,一下几种异常...

HostSugar ⋅ 2014/12/03 ⋅ 1

jvm调优经验分享

当Java程序申请内存,超出VM可分配内纯的时候,VM首先可能会GC,如果GC完还是不够,或者申请的直接超够VM可能有的,就会抛出内 存溢出异常。从VM规范中我们可以得到,一下几种异常...

不正经啊不正经 ⋅ 2014/12/25 ⋅ 0

JVM学习之:你了解OutOfMemoryError吗?

工作中或者在自己写的Demo中经常会出现OutOfMemoryError,从字面的意思看就是内存不够用了,可是往往越是经常看到的问题越不会留心的去观察他,就我本人而言OutOfMemoryError就是一个很好的例子...

sun7545526 ⋅ 2012/08/09 ⋅ 0

异常、堆内存溢出、OOM的几种情况

1、堆内存溢出 【情况一】:   Java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环;   如果是java堆内存不够的话,可...

www19 ⋅ 2017/06/14 ⋅ 0

JAVA内存区域及使用分配-实战JVM(一)

JAVA虚拟机运行时会将JVM使用的内存划分为不同的区域,每个区域负责不同的功能,以及各个区域的创建,销毁都各不相同。 下图是JVM运行时内存数据区的划分, 图1、JVM运行时数据区 1、程序计数...

杨小杨 ⋅ 2015/10/08 ⋅ 0

Java面试实战之OOM异常

今天是周末,祝大家周末愉快,11月3日更新了极限挑战,看的我是感触良多,心中还是澎湃的不行。看到时光的飞逝,让人更加的感受到,这就是生活。所以也希望大家能够通过总结之后珍惜良多的时...

cnJason ⋅ 2017/11/06 ⋅ 0

java虚拟机运行时的内存分类以及出现异常分析(jvm之一)

java虚拟机所管理的内存包括以下几个运行时数据区域: 方法区(Method Area):线程共享的,存放已被虚拟机记载的类信息、常量、静态变量等数据。“永久代(Permanent Generation)” 虚拟机...

zhengDavid ⋅ 2012/06/13 ⋅ 0

OutOfMemoryError详解

絮絮叨叨 最近在看周志明的《深入理解Java虚拟机》,虽然刚刚开始看,但是觉得还是一本不错的书。对于和我一样对于JVM了解不深,有志进一步了解的人算是一本不错的书。注明:不是书托,同样是...

六只 ⋅ 2012/07/04 ⋅ 3

Java 常见内存溢出异常与代码实现

Java 堆 OutOfMemoryError Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的对象过多时, 会导致 ...

手掌大人 ⋅ 2016/10/23 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Boost库编译应用

版本:Boost 1.66.0 Windows库编译 官网指南:直接执行bootstrap.bat处理文件即可,可以我却遇到一堆的问题。 环境:Windows 10 + Visual Studio 2017 Boost编译出来库命名 boost库生成文件命...

水海云 ⋅ 15分钟前 ⋅ 0

解决Eclipse发布到Tomcat丢失依赖jar包的问题

如果jar文件是以外部依赖的形式导入的。Eclipse将web项目发布到Tomcat时,是不会自动发布这些依赖的。 可以通过Eclipse在项目上右击 - Propertics - Deployment Assembly,添加“Java Build ...

ArlenXu ⋅ 15分钟前 ⋅ 0

iview tree组件层级过多时可左右滚动

使用vue+iview的tree组件,iview官网iview的tree树形控件 问题描述:tree层级过多时左右不可滚动 问题解决:修改overflow属性值 .el-tree-node>.el-tree-node_children { overflow: vi...

YXMBetter ⋅ 17分钟前 ⋅ 0

分布式锁

1.通过数据库实现 http://www.weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/ 2.ZK实现:curator-recipes分布式锁的......

素雷 ⋅ 26分钟前 ⋅ 0

Sublime Text3 快捷键

选择类 Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本。 Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑。举个栗子:快速选中并更改所有相同的变量名...

AndyZhouX ⋅ 32分钟前 ⋅ 0

XamarinAndroid组件教程RecylerView自定义适配器动画

XamarinAndroid组件教程RecylerView自定义适配器动画 如果RecyclerViewAnimators.Adapters命名空间中没有所需要的适配器动画,开发者可以自定义动画。此时,需要让自定义的动画继承Animation...

大学霸 ⋅ 33分钟前 ⋅ 0

eureka 基础(二)

使用Eureka服务器进行身份验证 如果其中一个eureka.client.serviceUrl.defaultZone网址中包含一个凭据(如http://user:password@localhost:8761/eureka)),HTTP基本身份验证将自动添加到您...

明理萝 ⋅ 36分钟前 ⋅ 1

Kubernetes(五) - Service

Kubernetes解决的另外一个痛点就是服务发现,服务发现机制和容器开放访问都是通过Service来实现的,把Deployment和Service关联起来只需要Label标签相同就可以关联起来形成负载均衡,基于kuberne...

喵了_个咪 ⋅ 36分钟前 ⋅ 0

更新队友POM文件后报错

打开报错的地方的pom及其引用方法所在文件的pom,观察其版本号是否一致,不一致进行更改

森火 ⋅ 49分钟前 ⋅ 0

IDEA使用sonarLint

一、IDEA如何安装SonarLint插件 1.打开 Idea 2.点击【File】 3.点击【Settings】 4.点击【Plugins】 5.在搜索栏中输入“sonarlint”关键字 6.点击【Install】进行安装 7.重启Idea 二、IDEA如...

开源中国成都区源花 ⋅ 54分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部