JVM 内存区域模型

作者: Aiibai | 来源:发表于2018-12-17 10:28 被阅读17次
1.运行时内存区域的划分

java 虚拟机在执行 java 程序的过程中,会将它管理的内存区域划分为若干个不同的数据区域,这些区域有各自不同的用途,以及创建和销毁时间。

image.png

HotSpot 虚拟机并不区分虚拟机栈和本地方法栈,所以下面统称这两种内存区域叫 Java 栈

2.每个内存区域的作用以及服务的对象
  • 程序计数器
    程序计数器是一块较小的内存空间,它可以看做是当前线程所执行字节码的行号指示器。如果线程正在执行的是一个 java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是 Native 方法,这个计数器的的值为空(Undefined)。

  • Java 虚拟机栈
    虚拟机栈描述的是 Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈。
    局部变量表存放了编译期可知的各种基本数据类型以及对象的引用。其中 64 位长度的 longdouble 类型的数据会占用 2 个局部变量空间,其余数据类型只占用 1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

  • 本地方法栈
    本地方法栈和虚拟机栈所发挥的作用是非常类似的,只不过虚拟机栈是为虚拟机执行 java 方法服务的,而本地方法栈是为虚拟机使用到的 Native 方法服务的。


  • 堆存放的是对象的实例,也是垃圾收集器管理的主要区域。根据 Java 虚拟机规范规定, Java 堆可以处于物理上不连续的内存空间,只要在逻辑上连续即可。

  • 方法区
    它用于存储被虚拟机加载的类信息,常量,静态变量以及即时编译器编译后的代码等。类似的,Java 虚拟机规范规定,方法区可以处于物理上不连续的内存空间,只要逻辑上连续就可以。另外,方法区可以选择不实现垃圾收集器,因为垃圾收集行为在这个区域比较少见,这个区域的内存回收目标主要是针对常量池和类型卸载。

  • 直接内存
    直接内存并不是虚拟机运行时数据区域,也不是 Java 虚拟机规范定义的内存区域,也叫做堆外内存。

3.可能抛出的异常
  • 程序计数器
    唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

  • Java 栈(虚拟机栈和本地方法栈)
    有两种可能抛出的异常:
    OutOfMemoryError:如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时。(如:创建的线程太多)
    StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度时。(如:没有终止条件的递归调用)


  • OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时。

  • 方法区
    OutOfMemoryError:当方法区无法满足内存分配需求时。

4.控制的参数
  • Java 堆
    -Xms 最小堆大小
    -Xmx 最大堆大小
    如果两个值相等,堆不能自动扩展,是固定大小。

  • Java 栈(虚拟机栈和本地方法栈)
    -Xss 栈大小

-方法区
-XX:PermSize 最小方法区大小
-XX:MaxPermSize 最大方法区大小

5.模拟异常

  • OutOfMemoryError
/**
 * -Xms10m
 * -Xmx10m
 * */
public class HeapOOM {
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }

    static class OOMObject {

    }
}

运行结果

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at jvm.exception.HeapOOM.main(HeapOOM.java:13)

解决这种溢出的思路是:首先确定是内存泄露还是内存溢出导致的异常。可以通过堆转储快照进行分析,如果是内存泄露导致的,就需要检查泄露的对象为什么不能被垃圾回收,也即寻找泄露对象与 GC Roots 的可达路径。如果是内存溢出查看


  • OutOfMemoryError
/**
 * -Xss128k
 */
public class StackOOM {
    public static void main(String[] args) {
        while (true) {
            new Thread(() -> {
                while (true){
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //...
                }
            }).start();
        }
    }
}

执行结果:

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at jvm.exception.StackOverflow.main(StackOverflow.java:23)

StackOverflowError

/**
 * -Xss128k
 */
public class StackOverflow {
    public static void main(String[] args) {
        test();
    }
    public static void test(){
        test();
    }
}

执行结果:

Exception in thread "main" java.lang.StackOverflowError
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
    at jvm.exception.StackOverflow.test(StackOverflow.java:11)
....
  • 方法区
    OutOfMemoryError:可以用两种方式导致这种异常,增加更多的常量,创建更多的代理类。
/**
 * -XX:PermSize=10M  -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        List<String> values = new ArrayList<>();

        int i=0;
        while (true){
            values.add(String.valueOf(i++).intern());
        }
    }
}

上面代码中的 intern 方法的作用是:判断是否存在需要的常量,如果存在就返回,如果不存在,则将当前值加入到常量池中。

执行结果:
发现设置了 -XX:PermSize和-XX:MaxPermSize 并不会导致方法区溢出。原来在 java8 中移除了这两个参数,原来的永久代(PermGen)概念也没有了,取而代之的是元空间(Metaspace)。不过它们也有一些区别:原先永生代中类的元信息会被放入本地内存(元数据区),将类的静态变量和内部字符串放入到java堆中。

新的元空间设置参数:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对改值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。

总结起来,java8中对方法区的实现做了如下的变化:

  • 移除了永久代(PermGen),替换为元空间(Metaspace);
  • 永久代中的 class metadata 转移到了 native memory(本地内存,而不是虚拟机);
  • 永久代中的 interned Stringsclass static variables 转移到了 Java heap
  • 永久代参数 (PermSize MaxPermSize) -> 元空间参数(MetaspaceSize MaxMetaspaceSize

那么,很明显上面的代码中的字符串常量是被放到了堆中了,增加参数-Xms10M和-Xmx10M,验证效果如下:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
Disconnected from the target VM, address: '127.0.0.1:53648', transport: 'socket'
    at java.nio.CharBuffer.wrap(CharBuffer.java:373)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:265)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.PrintStream.newLine(PrintStream.java:545)
    at java.io.PrintStream.println(PrintStream.java:807)
    at jvm.exception.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize1=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0

在上面的结果中我们不仅看到了 OutOfMemoryError ,还有一句 GC overhead limit exceeded ,这句是什么意思的?
GC overhead limt exceed 检查是 Hotspot VM 1.6 定义的一个策略,通过统计 GC 时间来预测是否要 OOM 了,提前抛出异常,防止 OOM 发生。Sun 官方对此的定义是:并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。“

/**
 * //-XX:PermSize=128k  -XX:MaxPermSize=128k
 * -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
 */
public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            enhancer.create();
        }
    }

    static class OOMObject {

    }
}

执行结果:

Error occurred during initialization of VM
OutOfMemoryError: Metaspace
6.对象访问
Object obj = new Object();

上面这行代码涉及到的内存区域有:方法区、堆、栈。
Object obj 这部分的语义将反映到 Java 栈的本地变量表 中。
new Object() 这部分的语义将反映到 Java 堆 中。
另外,还需要类型数据(对象类型、父类、实现的接口、方法等),这部分数据储存在 方法区

obj 中的引用代表的含义在不同的虚拟机中有不同的实现,主流的有两种:句柄和指针。

  • 句柄
    Java 堆 中将会划分出一块内存来作为句柄池,obj 中存储的是句柄地址,而句柄中包含对象的实例数据和类型数据的具体地址。
    image.png
  • 指针
    直接指针访问的方式中,类型数据的信息时存储在对象中, obj 中直接存储的是对象的地址。
    image.png

这两种方式都有各自的优缺点
在对象被移动式,只要修改句柄中的对象对象地址即可,而 obj 中的句柄地址不用改变。但是直接指针方法的速度更快,它节省的一次指针定位的时间开销。

参考:
https://blog.csdn.net/laomo_bible/article/details/83067810
https://www.cnblogs.com/hucn/p/3572384.html

相关文章

  • jvm笔记

    JVM的内存模型是什么样子的? JVM内存模型可以大致可划分为线程私有区域和共享区域,线程私有区域由虚拟机栈、本地...

  • JMM与可见性

    JMM jvm运行时数据区域 Java 内存区域和内存模型是不一样的东西,内存区域是指 Jvm 运行时将数据分区域...

  • 8. JVM Memory Model and Visibili

    前言:JVM内存模型、Java内存区域、GC分代回收容易搞混。前面讲解了JVM内存区域,它是Java代码编译成.c...

  • JVM内存模型(jvm 入门篇)

    概述 jvm 入门篇,想要学习jvm,必须先得了解JVM内存模型,JVM内存模型,JVM内存模型,JVM内存模型,...

  • JVM之内存模型以及各种溢出异常

    近期学习了JVM,借此整理一下JVM有关的内存模型和各种内存溢出。 运行时数据区域 要理解Java的内存模型,作者...

  • JVM 内存区域模型

    1.运行时内存区域的划分 java 虚拟机在执行 java 程序的过程中,会将它管理的内存区域划分为若干个不同的数...

  • Java成神之路(一)-- JVM基础

    JVM内存模型### 描述主内存和工作内存之间的通信规则,避免数据不一致。所有线程共享JVM内存区域main me...

  • 阿里面试题若干 2020

    JVM内存模型及分区 Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的...

  • JVM系列:(八)JVM内存模型

    原文链接:JVM系列:(八)JVM内存模型 一 运行时数据区域 类加载器加载的class字节码保存到JVM内存的方...

  • Java内存区域(运行时数据区域)和内存模型(JMM)

    Java 内存区域和内存模型是不一样的东西,内存区域是指 Jvm 运行时将数据分区域存储,强调对内存空间的划分。 ...

网友评论

    本文标题:JVM 内存区域模型

    本文链接:https://www.haomeiwen.com/subject/vcrqkqtx.html