深入JVM——OOM异常解析

JVM对象访问解析

  • 对象访问过程的内存情况
public void function(){
    Object obj = new Object();
}

function方法被执行的时候,JVM在JVM栈中为function创建一个栈帧,用于存放function在运行过程中的一些信息。 Object obj被执行时,JVM在function方法对应的栈帧中的本地变量表中创建Object类型的引用obj。 new Object()被执行时,JVM在堆内存中创建一块Object类型的、包含实例数据值的结构化内存。 PS:不同的类型所对应的这块结构化内存的长度是不一样的。

  • 对象访问的方式 不同的虚拟机的对象访问方式有所不同,主流的访问方式有两种:使用句柄间接访问实例数据、指针直接访问实例数据。 a)指针直接访问实例数据 在这种方式中,JVM栈中的栈帧中的本地变量表中所存储的引用地址就是实例数据的地址。通过这个引用就能直接获取到实例数据的地址。 除此之外,其实引用所指向的对内存中的对象数据有两部分组成,一部分就是这个对象实例本身,另一部分是对象类型在方法区中的地址。 b)使用句柄间接访问实例数据 JVM会在堆中划分一块内存来作为句柄池,JVM栈中的栈帧中的本地变量表中所存储的引用地址是这个对象所对应的句柄地址,而非对象本身的地址。句柄池中的一个个对象地址有两部分组成,一部分就是对象数据在堆内存中实例池中的地址,另一部分就是对象类型在方法区中的地址。

综上所述:句柄池就相当于一个中转站,我们要查询的实例对象数据需要通过一次间接索引获取;而指针的直接访问无需中转站,引用指向的就是要访问的实例对象数据。

此外,不管是哪种对象的访问方式,引用所指向的堆内存中的数据都是有两部分组成,其中有一部分一定是一个指向方法区中的对象类型的指针。

  • 两种对象访问方式的好处 a)句柄访问对象的好处 访问对象通过一个句柄指针一次间接索引之后,当对象实例数据被移动的时候(垃圾回收的时候有些对象会被移动),只需要改变句柄池中该对象实例的地址即可,无需改变引用和句柄池的对应关系,所以引用中存储的是稳定的句柄地址。 b)指针直接访问对象的好处 这种方式最大的好处就是访问对象的速度很快,比通过句柄访问对象节约了一半的寻址时间,由于Java中对象的访问非常频繁,所以这种方式能节约很多寻址时间。

OOM异常解析

  • 堆内存的OOM异常 a)如何产生? 堆内存用于存储实例对象,当我们不断创建对象,并且对象都有引用指向(GC Roots到对象之间有可达路径),那么垃圾回收机制就不会清理这些对象,当对象多到挤满堆内存的上限后,就产生OOM异常。 b)模拟堆内存OOM异常 PS:在eclipse的Arguments中可以设置VM arguments,这就是JVM的一些参数。 -Xms:设置堆的最小值 -Xmx:设置堆的最大值
public class A(){
    public static void main(String[] args){
        while(true){
            new Person();
        }
    }
}
//运行结果中出现:java.lang.OutOFMemory:Java heap space
//说明是在堆内存中发生了OOM异常。

; c)如何解决? 使用内存映像分析工具:Eclipse Memory Analyzer对dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,即要搞明白是内存泄漏还是内存溢出。 PS:内存泄漏导致的OOM:new出来的很多对象已经不需要了,但仍然有引用指向,所以垃圾回收机制无法回收。 PS:内存溢出:new出来的对象都是需要的,但堆内存太小装不下了。 如果是内存泄漏,通过工具查看泄漏对象到GC Roots的引用链。找到泄漏对象是通过怎样的路径与GC Roots发生关联,然后导致垃圾回收机制无法自动回收的。 如果不存在内存泄漏,也就是所有的对象都必须存在,这时候就调大堆内存。

  • JVM栈和本地方法栈的OOM异常 a)StackOverFlowError 当线程请求的栈深度大于虚拟机所允许的最大栈深度,就会抛出这个异常。 b)OutOfMemeoryError 当虚拟机要扩展栈时无法申请到足够空间的内存,就会抛出这个异常。 PS:这两种异常其实是对同一个问题的两种描述。在单一线程下,不论是栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverFlowError。通过测试发现,如果给每个线程的JVM栈分配的内存越大,大的栈帧在这个JVM栈中也能装得下,理应StackOverFlowError会减少,但事实却恰恰相反:当每个线程的JVM栈越大,那么所能创建的线程数就越少,稍微建立几个线程可能就会把有限的内存资源耗尽。
  • 运行时常量池的OOM异常 我们通过String类的intern()方法向方法区中的常量池添加内容。 intern方法的作用是:当常量池中已经有这个String类型所对应的字符串的话,就返回这个字符串的引用;如果常量池中没有这个字符串的话就将这个字符串添加到常量池中,再返回这个字符串的引用。
  • 方法区的OOM异常 a)如何产生? 方法区中存放的是Class的相关信息,如:类名、访问修饰符、常量池、字段描述、方法描述等。 如果产生大量的类就有可能将方法区填满,从而产生方法区的OOM异常。 b)注意点 方法区的OOM异常是非常常见的,特别是在一些动态生成大量Class的应用中(JSP),需要特别注意类的回收。
  • 本机直接内存的OOM异常

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java、Spring、技术分享

JVM学习笔记

  java引用类型分为四种:类、接口、数组类和泛型参数。其中泛型参数会在编译过程中被擦除。因此 Java 虚拟机实际上只有前三种。在类、接口和数组类中,数组类...

1032
来自专栏程序员的SOD蜜

实例探究字符编码:unicode,utf-8,default,gb2312 的区别

 最近做邮件收发,不同的邮件系统间可能会出现编码问题,迫使我重新回来研究一下字符的编码问题,unicode,utf-8,gb2312这些编码格式都是我们熟知的,...

23510
来自专栏JAVA高级架构

【深入Java虚拟机】之一:Java内存区域与内存溢出

内存区域 Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。Java虚拟机规范将JVM所管理的内存分为以下几个运行时...

3406
来自专栏奔跑的蛙牛技术博客

什么是字节码?

字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。字节是电脑里的数据量单位。

3413
来自专栏QQ音乐技术团队的专栏

谁创建谁销毁,谁分配谁释放——JNI调用时的内存管理

在QQ音乐AndroidTV端的Cocos版本的开发过程中,我们希望尽量多的复用现有的业务逻辑,避免重复制造轮子。因此,我们使用了大量的JNI调用,来实现Jav...

4146
来自专栏Java技术栈

多线程 start 和 run 方法到底有什么区别?

昨天栈长介绍了《Java多线程可以分组,还能这样玩!》线程分组的妙用。今天,栈长会详细介绍 Java 中的多线程 start() 和 run() 两个方法,Ja...

1691
来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列11

Java面试系列11 1 Java中的异常处理机制的简单原理和应用 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。...

3618
来自专栏闪电gogogo的专栏

Python初学——多进程Multiprocessing

1.1 什么是 Multiprocessing 多线程在同一时间只能处理一个任务。 可把任务平均分配给每个核,而每个核具有自己的运算空间。 1.2 添加进程 P...

3378
来自专栏用户2442861的专栏

Java多线程学习(吐血超详细总结)

http://blog.csdn.net/evankaka/article/details/44153709

7581
来自专栏向治洪

java虚拟机构造原理

 Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三...

1956

扫码关注云+社区