资讯专栏INFORMATION COLUMN

Java内存区域与内存溢出

LiuZh / 573人阅读

摘要:前言最近在读周志明老师的深入理解虚拟机感觉一下换了一个角度来看待代码,有必要整理一些内容,更清楚实际的流程,这一篇就记录下内存区域与相关的一些内存溢出的异常。除了这些以外,直接内存的不合理分配也会影响到虚拟机动态扩展内存时出现内存溢出。

前言

最近在读周志明老师的《深入理解Java虚拟机》,感觉一下换了一个角度来看待Java代码,有必要整理一些内容,更清楚实际的流程,这一篇就记录下Java内存区域与相关的一些内存溢出的异常。

内存区域

Java虚拟机在执行Java程序的过程会把它管理的内存划分为各个不同的区域,这些区域都有着各自的生命周期,总的来说Java虚拟机管理的内存将会包括一下的数据区域

图中可以很清晰的看出区域里面各个实体的关系,然后简单介绍一3下各个实体。

1.程序计数器

线程私有,可以看作是当前线程所执行的字节码的行号指示器,字节码解释器的工作就是通过改变计数器的值来进行后续的操作。

2.虚拟机栈

线程私有,描述的是Java方法执行的内存模型,每个方法在执行的同时会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息。

3.本地方法栈

与虚拟机栈发挥的作用类似,但虚拟机栈主要是为执行Java方法服务,而本地方法栈则为虚拟机使用到的native方法服务。

4.Java堆

是Java虚拟机所管理的内存中最大的一块,也是被所有线程共享的一块内存区域,在虚拟机启动时创建,此区域唯一目的是存放对象实例。

5.方法区

也是各个线程共享,用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,为堆的一个逻辑部分。其中的运行时常量池相对于Class文件常量池而言具备动态性,运行期间也可将新的常量放入池中。
除了这些以外,直接内存的不合理分配也会影响到Java虚拟机动态扩展内存时出现内存溢出。

从JVM看对象

Java对象的创建我们无时无刻都在使用着,这里从虚拟机层面来看待对象的创建。

对象的创建

1.当虚拟机收到一条new指令时,首先去检查这个指令的参数能否在常量池中定位到对应的符号引用,并且检查符号引用代表的类是否加载过、解析和初始化过,没有则进行对应的类加载过程。

2.在类检查通过后,虚拟机为新生对象从Java堆中分配内存。而内存的分配又有两种方式。一种是指针碰撞,就是把空闲和正在使用的内存中间放一个指针隔开,所以这种方式实际就是把指针进行移动,当内存出现相互交错时。该方式自然就行不通了;另一种方式是空闲列表,由虚拟机维护一个列表,分配内存后就更新这个列表的记录。至于使用哪种方式就要看是基于何种垃圾回收器而言。除了怎么划分可用空间之外,还需要考虑虚拟机分配内存的频率,分配频率就会涉及到并发中的一些问题了。JVM采用CAS(比较并交换)方式进行失败重试保证操作的原子性;另一种是每个线程在Java堆中预先分配一小块内存,也就是本地线程分配缓冲(TLAB)

3.内存分配完成后,虚拟机将分配到的内存空间都初始化为零值,使用TLAB,则可以提前到分配时进行。

4.虚拟机对对象进行必要的设置,也就是把该对象相关的信息存储在对象头之中,这些工作都完成后,一个新的对象已经产生了,接下来就是对象的初始化了。

对象的访问定位

对象的访问目前主流的方式有句柄和直接指针两种。
1.使用句柄访问,Java堆会划分出一块内存作为句柄池,对象的引用中存储的就是其句柄的地址,句柄包含了对象实例数据和类型数据的具体地址信息。
2.使用直接指针,堆就要考虑如何放置访问类型数据相关的信息,引用中存储的直接就是对象地址。
很显然,因为是直接指向地址,所以直接指针的方式更加快,但对象访问在Java中太过频繁,积少成多后也会造成较高的执行成本。

内存溢出异常 1.Java堆溢出
public class HeapOOM {

    static class OOMObject {

    }

    public static void main(String[] args) {
        List list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

这里可以设置下虚拟机相关的参数,

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
2.虚拟机和本地方法栈溢出
public class JavaVMStackSOF {

    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF stackSOF = new JavaVMStackSOF();

        try {
            stackSOF.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack length: "+stackSOF.stackLength );
            throw e;
        }
    }
}
3.创建线程导致的内存溢出
public class JavaVMStackOOM {

    private void doStop() {
        while (true) {

        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(()->{
               doStop();
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

这里的话,在Windows平台的虚拟机中,Java的线程是映射到操作系统的内核线程上,这里可能导致机器假死。

4.方法和运行时常量池溢出
public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        List list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}
5.本机直接内存溢出
public class DirectMemoryOOM {

    public static final int _1MB = 1024 * 1024;
    public static void main(String[] args) {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        try {
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
            while (true) {
                unsafe.allocateMemory(_1MB);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
总结

了解了这些不得不佩服设计者,一个看似简单的东西底层要解决的问题、处理的细节也是非常多的,从Java虚拟机层面看到了不一样的东西,也是非常有意思的,理解底层实现的原理有助于写成更健壮的代码,更快速的debug,就到这里了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71073.html

相关文章

  • 十种JVM内存溢出的情况,你碰到过几种?

    摘要:内存溢出的情况就是从类加载器加载的时候开始出现的,内存溢出分为两大类和。以下举出个内存溢出的情况,并通过实例代码的方式讲解了是如何出现内存溢出的。内存溢出问题描述元空间的溢出,系统会抛出。这样就会造成栈的内存溢出。 导言: 对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不需要自己实现释放内存,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存这一切看起来非常美好,但是一旦...

    ShevaKuilin 评论0 收藏0
  • Java虚拟机笔记-内存内存溢出

    摘要:小结程序计数器和虚拟机栈是线程私有的,而堆和方法区是线程共享的除了虚拟机运行时内存,在中使用类可以直接操作本机内存。 Java的内存区域 Java虚拟机在执行Java程序中会把它所管理的内存划分为若干个数据区域,这些区域有各自的用途,以及生命周期,有些依赖虚拟机进程启动而存在,有些依赖用户线程的启动和结束而建立和销毁 运行时内存 showImg(/img/bVqUpc); 程序计数器(...

    Ocean 评论0 收藏0
  • Java内存区域内存溢出

    摘要:直接通过可以造成本机内存溢出。小节内存区域描述异常程序计数器略略略虚拟机栈存放编译器可知的各种基本类型,对象引用和类型每个线程的栈大小堆存放对象实例最大值最小值运行时常亮池存放编译期生成的字面量和符号引用,运行期也能放入常量池。 堆溢出 Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径避免垃圾回收,当到达最大堆的容量限制后就会产生Java.l...

    cheukyin 评论0 收藏0
  • JVM系列(一):深入详解JVM 内存区域总结!

    摘要:一内存区域虚拟机在运行时,会把内存空间分为若干个区域,根据虚拟机规范版的规定,虚拟机所管理的内存区域分为如下部分方法区堆内存虚拟机栈本地方法栈程序计数器。前言 在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的。所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题。但是,一旦JVM发生这些情况的时候,如果你不清楚JVM内存的...

    Aldous 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<