美文网首页
JVM系列之内存结构运行时数据区

JVM系列之内存结构运行时数据区

作者: 阿伦故事2019 | 来源:发表于2019-11-02 12:04 被阅读0次

    布朗大学(美国)校训:“我们信赖上帝。”


    转眼已进入深秋,在书房里听听歌写写文章,蛮舒服惬意的事情!

    继续聊jvm系列,上两节聊了class文件结构和类加载机制,接下来聊聊面试的高频点,jvm的内存结构模型,及实战中对运行时数据区的理解,闲话不多说,先来一张图吧,请阅: 内存结构规范.png 这张图基本把前面讲的也一起串起来了,先是静态编译把源码编译成class字节码文件,再通过加载器加载进内存,接下来就进入了运行时数据区域了,这块即是jvm内存结构规范,把此区域划分为堆/方法区/栈区(本地方法栈/虚拟机栈/程序计数器),其中栈区为线程私有,堆和方法区为线程共享。接下来就重点讲解下这几个区域:

    一 Java堆(Heap)

    这个大家估计耳熟能详,随口便说是放置new对象的地方,没错,主要是存储对象实例,几乎所有的对象实例都在这个区域分配内存。看到这里估计有朋友问了,那是不是有的对象实例不是在堆上分配的,jvm还真提供了,是否听说过栈上分配和TLAB,栈上分配是为了减少gc的压力,因为随栈弹出而释放并不会出发gc,TLAB则是优化对象实例在堆中并发分配的效率,具体详细可自行百度了解,这里放一张对象分配图,摘自实战Java虚拟机一书中: 对象分配.png

    堆是jvm管理内存最大的一块,是线程共享的一块区域,在jvm启动时创建;可能有些同学经常遇到OOM(OutOfMemoryError)异常,这里考大家一题,如何模拟OOM?
    很简单,首先设置下jvm参数(-Xms 1m -Xmx 2m),然后在main函数初始化一个大的数组即可浮现。

    二 方法区(Method Area)

    与堆一样,也是被线程共享的区域,同样会出现OOM(OutOfMemoryError)异常,此区域主要存储如下信息:
    1/已被虚拟机加载的类信息,即元数据;
    2/静态变量和常量,这里维护一个运行时常量池;
    3/JIT(即时编译)后的代码,这个上节有提到过;
    这里,分享一个常考的面试题:

    /**
         * @author 阿伦故事
         * @Description:
         *  小试牛刀
         * */
        public static void main(String[] args) {
            String s = "1"+"2"+"3";// 1 这里创建了几个对象?
             
            String s1 = "allen story";// 2 
            String s2 = new String("allen story");// 3 这里创建了几个对象?
        }
        /**
         * 分析如下:
         * 1处只创建了一个对象,即“123”在运行时常量池,栈中引用s直接指向常量池
         * 3处也只创建一个对象,即在堆中new了一个对象实例,“allen story”在2处已
         * 经在运行时创建,堆中对象直接存储该指向即可。
         * */
    

    三 程序计数器(Program Counter Register)

    了解jvm内存结构规范的朋友们,肯定脱口而出,pc用于记录所属线程所执行的字节码的执行行号,没错,有多少朋友理解这个意思?如果了解寄存器的话,那就容易很多,cpu执行的指令就是从指令寄存器取的,而pc就是用于存储下一条指令的偏移地址。当然,pc是线程私有的,存储的自然所属线程的指令地址。
    唯一一个在JVM规范中没有规定任何OutOfMemoryError的区域。
    如何理解?
    很简单,pc只存储下一条指令的地址,并不会随着指令量级的增长而增长。

    四 栈(Stack)

    栈分为本地方法栈和虚拟机栈,顾名思义,一个是用于native方法的,栈的特性是先进后出,这也是递归调用的逻辑。栈是线程私有的,每启动一个新线程时,jvm都会为它分配一个Java栈,以栈帧为单位保存线程的运行状态。
    虚拟机只会对Java栈执行两种操作:以栈帧为单位的入栈或者出栈。
    这里也容易出现一个异常,StackOverflowError,如何模拟?
    理解了就很容易,超过了栈深度不就异常了嘛?
    只需要写个递归调用,无返回不退出,一直调用无退出条件,那不就一直在入栈而不弹栈,那不栈溢出了嘛?另可通过测试得知jvm为每个线程分配的内存默认是1M,具体可测试下,即jvm参数(-Xss 1m)。
    栈在jvm内存结构中非常重要,我们有必要详细了解它的组成部分,由基本类型变量区、执行环境上下文、操作指令区(存放操作指令)组成,只是保存基本类型数据和自定义对象的引用,即堆中实例对象的地址或偏移地址。

    五 小结

    介绍了jvm内存结构划分,稍稍总结下重点区域,堆/方法区/栈之间的关系(如图),以一个例子串下类的加载/分配与执行。


    内存结构关系.png

    Sample:

    /**
         * @author 阿伦故事
         * @Description:
         *  jvm执行流程
         * */
        public static void main(String[] args) {
            Person person = new Person();
             person.sayHello();
        }
        /**
         * 流程如下:
         * 上述很简单,就是main主线程创建一个person对象实例,调用其sayHello方法
         * 1/先去方法区寻找Person类的元数据信息;
         * 2/如果找不到,Classloader加载Person类信息存储内存方法区;
         * 3/在堆中创建Person对象,并持有方法区中Person的元信息的引用;
         * 4/把引用变量person添加到栈中,指向堆中的内存对象Person实例;
         * 5/执行person.sayHello()时,JVM根据person定位到堆空间的Person实例;
         * 6/根据Person实例持有的方法区引用,获取Person元信息与sayHello方法执行字节码。
         * */
    
    执行流程.png

    特此声明:
    分享文章有完整的知识架构图,将从以下几个方面系统展开:
    1 基础(Linux/Spring boot/并发)
    2 性能调优(jvm/tomcat/mysql)
    3 高并发分布式
    4 微服务体系
    如果您觉得文章不错,请关注阿伦故事,您的支持是我坚持的莫大动力,在此受小弟一拜!

    相关文章

      网友评论

          本文标题:JVM系列之内存结构运行时数据区

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