美文网首页
深入理解JVM内存模型

深入理解JVM内存模型

作者: 陈二狗想吃肉 | 来源:发表于2021-03-23 17:04 被阅读0次

    JVM 内存模型

    运行时数据区域

    程序计数器(Program Conunter Regisiter)

    程序计数器是一个比较小的内存空间,可以看作是当前线程执行的字节码行号指示器。本质就是记录字节码执行顺序。 在《Java 虚拟机规范》中没有任何 OutOfMemoryError 情况的区域。

    虚拟机栈(JVM Stack)

    虚拟机栈,存放的是线程运行时内部的局部变量,也可以理解为线程栈。

    每个方法被执行的时候, 虚拟机会创建一个栈帧(Stack Frame)用于存放局部变量表(local variable),操作数栈(operand stack),动态连接,方法出口等信息。

    栈帧(Stack Frame)随着方法的调用而创建,随着方法的结束而销毁(不论是正常结束还抛出异常)。

    字节码指令分析(描述 JVM Stack 操作过程)

    publicintadd(){inta =1;intb =2;intc = b - a;returnc;}0iconst_1//将 a 压入局部变量表栈顶1istore_1//对 a 进行赋值 1  2iconst_2//将 b 压入局部变量表栈顶3istore_2//对 b 进行赋值 24iload_2//读取 b 到操作数栈5iload_1//读取 a 到操作数栈6isub//执行 b - a7istore_3//将 int 类型的值存入局部变量表 3 8iload_3//读取 c 到操作数栈9ireturn//返回复制代码

    局部变量表(local variable)

    局部变量表存放了各种编译期 Java 虚拟机基本数据库类型(boolean、byte、char、short、int、float、long、dubble)和对象引用(reference 类型,即对象的起始位置指针或者对象句柄), 对象的真实数据通常存放在堆空间。

    局部变量表中的存储空间通过变量槽(slot) 来表示,其中 64 位长度的 long  和 double 占 2 个变量槽。

    操作数栈(operand stack)

    每个栈帧都包含一个操作数栈的先进先出(FIFO)栈,栈帧中操作数栈的深度由编译期决定,并且通过方法的 code 属性保存以及提供给栈帧使用。

    动态链接

    每个栈帧都包含一个指向当前方法所在类型的运行时常量池的引用。以便对当前方法的代码实现动态链接

    在 class 文件中,一个方法如果要调用其它方法, 或者访问局部成员变量,则需要将符号引用(synbolic reference)来表示,动态链接的作用就是将这些符号引用转换为对实际方法的直接引用

    方法出口

    方法正常完成,当前栈帧恢复调用者的责任,包括恢复调用者的局部变量表,操作数栈,以及正确的程序计数器递增。跳过刚才执行的方法调用指令等,低哦啊用着的代码被调用的方法正返回值压入调用者操作数栈后,会继续正常执行。

    方法一场完成,某些指令导致了 JVM 虚拟机抛出异常,或者用户显示的通过thorw关键字跑出一场,同时在改该方法中没有捕获异常。如果方法异常调用完成,那不一定有方法返回值返回给调用者。

    本地方法栈(Native Method Stack)

    为本地方法所分配的内存空间,就是为native关键字修饰的方法提供服务的。

    本地方法主要是 Java 来调用 C/C++ 函数库的调用方法。

    方法区(Method Area)

    主要存放数据有:常量,静态变量,类信息。

    方法区存放的是静态变量的内存地址, 方法区里面有一个元空间, 在JDK1.8 之前叫永久代。

    堆(Heap)

    JVM 管理的最大的一块内存空间。与堆相关的一个重要概念是垃圾收集器。几乎所有的垃圾收集器都是采用分代收集算法,所以对内存空间也是基于这一点进行相应的划分:新生代和老年代,新生代分为Eden 空间、From Survivor 空间、To Survivor 空间。

    对象创建的过程中首先会存在 Eden 区,然后经过 minor gc 过后进入 survivor ,进过 15 次 survivor 转移过后,进入老年代。

    如果内存都不够用了就触发 full gc, 再次触发 GC 过后无法分配申请内存,JVM 就会抛出 OOM。

    分析对象是否存在引用,是否被回收采用的是 GC ROOT 可达性分析。

    直接内存(Direact Memory)

    直接内存,不是 JVM 来管理,是通过操作系统来管理的, 与 Java NIO 密切相关。 Java 通过DirectByteBuffer来操作直接内存。

    JVM 内存参数设置

    内存参数配置

    Spring-Boot 程序的 JVM 内存参数设置格式(Tomcat 启动直接在 bin 目录下的 Catalina.sh 文件设置)

    java -Xms2048m -Xmx2048m  -Xmn1024 -Xss512k -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -jar  xxx-xxx.jar复制代码

    关于元空间JVM 有两个:-XX:MetaspaceSize=N 和 -XX:MaxMetaspaceSize=N,对于 64 位 JVM 来说, 元空间默认是 21MB,默认的元空间的最大值是无限

    -XX:MaxMetaspaceSize: 设置元空间最大值,默认是 -1, 即不限制,或者说是受限制于本地内存大小。

    -XX:MetaspaceSize:指定元空间的初始大小,以字节为单位,默认是 21M,达到该值过后就会触发 full gc 进行类型卸载,同时收集器会对该值进行调整;如果释放了大量的空间就适当降低该值;如果释放了很少的空间,那么就在不超过 -XX:MaxMetaspaceSize (如果设置)的情况下,适当提高该值。

    由于调整元空间大小需要 full gc , 这是一个非常昂贵的操作,如果在启动过程中发生大量 full gc, 通常都是由于永久代或者元空间发生了大小调整,基于这种情况,一般建议在 JVM 参数将 MaxMetaspaceSize 和 MetaspaceSize 设置成一样的值,并设置得比初始值要大,对于 8G 的物理内存来说我们通常都会将这两个值设置为 256M。

    堆空间内存溢出

    importjava.util.ArrayList;importjava.util.List;publicclassHeapOverFlowTest{byte[] a =newbyte[1024*1024*2];// 2mbpublicstaticvoidmain(String[] args){        List list =newArrayList<>();while(true) {            list.add(newHeapOverFlowTest());        }    }}// 输出结果Exception in thread"main"java.lang.OutOfMemoryError: Java heap spaceat cn.edu.cqvie.jvm.HeapOverFlowTest.(HeapOverFlowTest.java:8)at cn.edu.cqvie.jvm.HeapOverFlowTest.main(HeapOverFlowTest.java:13)复制代码

    虚拟机栈内存溢出

    publicclassStackOverFlowTest{// JVM 设置// -Xss128k, -Xss默认1Mstaticintcount =0;staticvoidredo(){        count++;        redo();    }publicstaticvoidmain(String[] args){try{            redo();            System.out.println(count);        }catch(Throwable t) {            t.printStackTrace();        }    }}// 输出结果: 栈溢出java.lang.StackOverflowErrorat cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)at cn.edu.cqvie.jvm.StackOverFlowTest.redo(StackOverFlowTest.java:11)....复制代码

    总结:

    -Xss 设置越小 count 值越小,说明一个线程栈里能够分配的栈帧就越小,但是对于 JVM 整体来说能够开启的线程数就会更多。

    方法区内存溢出

    需要注意的是 1.8 内模型中,将运行时常量池数据放入堆中,所以我们限制方法区的大小对运行时常量池的限制毫无意义。最终也只会抛出java.lang.OutOfMemoryError: Java heap space异常。

    下面通过GCLib 模拟方法区溢出模拟的一个例子。

    /**

    * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

    */publicclassMyTest4{publicstaticvoidmain(String[] args){for(; ; ) {            Enhancer enhancer =newEnhancer();            enhancer.setSuperclass(MyTest4.class);            enhancer.setUseCache(false);            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) ->                    proxy.invoke(obj, args1));            System.out.println("hello world");            enhancer.create();        }    }}//输出结果Caused by: java.lang.OutOfMemoryError: Metaspaceat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:756)  ......复制代码

    JVM 监控工具

    VisualVM

    VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。在 VisualVM 的图形用户界面中,可以方便、快捷地查看多个 Java 应用程序的相关信息。

    作者:大漠北

    链接:https://juejin.cn/post/6925664904833662983

    来源:掘金

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:深入理解JVM内存模型

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