深入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 条评论
登录 后参与评论

相关文章

扫码关注云+社区