JVM内存模型

JVM整体的内存模型如下图所示

JVM整体架构介绍

在JVM执行class文件的时候,首先会由类装载子系统将字节码文件装载到java虚拟机的运行时数据区,再由虚拟机的字节码执行引擎执行字节码文件,所以完整的java虚拟机由三部分组成:类加载子系统运行时数据区执行引擎

运行时数据区

运行时数据区由下面部分组成:

  • 栈 (线程栈)
  • 本地方法栈
  • 方法区(元空间)
  • 程序计数器

class Math {
    public static final Integer CONSTANT = 666;
    
    public int computer() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }
    
    public static void main(String[] args) {
        Math math = new Math();
        math.computer();
        System.out.println("test。。。");
    }
}

栈在这里其实叫线程栈,以主线程为例,在执行main方法的过程中,可能会产生一些局部变量,例如在上面的程序中,主线程里面就产生了math局部变量,这些变量就会被放到栈中。还有一种可能,我们在线程中执行了某些方法,而这些方法的内部也产生了很多的局部变量,例如上面在main方法中执行了computer方法,在该方法中产生了abc三个局部变量,那么这些局部变量也会被存入该线程栈中。

栈帧

上面提到了两个方法,maincomputer,在程序执行的过程中,main跟computer方法内部产生的局部变量都会被放入栈中,但是它们并不是乱放的,在栈里面,又会为main跟computer方法单独划分两块不同的区域分别存放局部变量,而划分出来的这块区域就叫做这个方法的栈帧,线程栈中的每一个方法都会对应一个栈帧

其实上面提到的局部变量也并不是胡乱的存放在栈帧中的,在栈帧中又划分除了几块区域专门存放特定数据,局部变量就存在于这几块区域中的局部变量表中

这几块区域分别为:

  • 局部变量表:这个区域就是用来存放局部变量的

  • 操作数栈:这个栈是用来存放数的,例如上面computer中的a、b、c这些变量存放在局部变量表,那么它们的值12等就会被压入操作数栈中

    • 下面分析computer中的程序,首先int a = 1做的操作就是将1压入操作数栈中,随后会将1出栈放到局部变量表中变量a对应的内存区域中;很显然int b = 2也会将2压入操作数栈,然后将2出栈放到局部变量表b对应的内存中

    • 这么看好像将1压栈又出栈的操作显得很多余,那操作数栈的意义是什么呢?接着往下执行程序,在执行a + b的时候,会先从局部变量表中取出a b的数据压入操作数栈中,然后执行iadd指令,这条执行会从操作数栈中弹出两个数进行加法运算,现在弹出来的两个数就是a跟b对应的值1和2,然后相加为3,接着将3压入操作数栈中,后面的乘法亦然

    • 其实到现在就可以了解到操作数栈其实就是程序运行中数据的一个中转存储区域

  • 动态链接:动态链接存放的是computer方法对应的JVM指令码的地址

  • 方法出口:在上面的mian方法中,执行完computer后要继续执行main方法后面的内容,后面继续执行的内容的代码行数就存在于方法出口中,这样在执行完computer方法后就会准确的知道接下来要执行的程序行数

本地方法栈

在java创造初期,还是c语言如日中天的时候,当时的java还是大量的调用了c的代码,很多native方法底层就是c语言,这些c语言代码运行过程中也会产生局部变量等一些列的数据,本地方法栈就是用来存储这些数据的

方法区(元空间)

上面提过在class文件经过类装载子系统装载后会将字节码文件装载到JVM运行时数据区中,具体是装载到运行时数据区中的方法区中,方法区主要存储常量、静态变量以及类元信息。

类元信息:类元信息主要记录该字节码文件中有哪些方法,哪些常量等一些类的组成结构。后面如果在程序中new了一些对象,这些对象会被放到堆中,但是堆中的对象会有一个对象头,对象头里面有一个类型指针,这个指针指向的就是方法区中该对象对应的类元信息,这样通过该对象就可以知道该对象可以执行哪些方法,获取哪些变量等等。

静态变量:静态变量有可能是对象类型的,这时在方法区中只持有一个引用,真正的对象在堆中,方法区的这个静态变量指向堆中具体的对象,像这种对象由于一直由引用,所以一直不会被minor gc回收,最终经过15次gc会被放入老年代中

我们通过new关键字创建的对象都会存放在堆中

堆内部分为了很多区域,主要有下面几个区域:

  • 年轻代(占1/3)

    • Eden区(占8/10)

    • Survivor区(占2/10)

      • From(占1/2)

      • To(占1/2)

  • 老年代(占2/3)

我们创建的对象一开始是会被存放在Eden区中的,如果Eden区的数据有一天存满了,那么就会触发执行引擎的minor gc区清理,这个gc会把那些没有被引用的对象(在栈帧的局部变量表中不存在一个变量引用这个堆中的对象)进行清除,对于不能被清除的对象,会将它们的对象分代年龄加一(对象分代年龄与类元指针一样存在于该对象的对象头中)。然后会将这些没有被清除的对象放到survivor区中的的From区,如果有一天From区也满了,还是会触发minor gc进行清理,没有被清理的对象会被放到To区(与此同时对象的分代年龄加一),等To区域满了,依旧触发minor gc清理,清理不了的放在From分区,然后在From区跟To区循环,当某个对象被清理15次,也就是对象分代年龄为15的时候还是不能被清除,那么该对象就会被放入老年代,成为"老不死"的对象。当老年代满了之后会触发full gc,full gc会对老年代的对象进行清理,如果这时对象仍不能被销毁,那么程序就会奔溃,抛出OutOfMemoryError的错误。

程序计数器

程序计数器是用来存放接下来要执行或者正在执行的JVM指令码

不同的线程拥有不同的程序计数器

 

posted @ 2019-10-30 20:38  Jin同学  阅读(161)  评论(0)    收藏  举报