Java 8 JVM内存模型

JVM内存模型图

JVM内存模型
这里介绍的是JDK1.8的内存模型,与之前的版本相比其最大的不同就是使用元空间取代了永久代。元空间与永久代类似,它们的主要区别在于元空间并不在JVM中,而是使用了本地内存。

各区域介绍

模型图中三个蓝色区域为每个线程独有的,而橙色区域则是所有线程共享的,即每个线程都有属于自己的程序计数器虚方法栈本地方法栈,而堆区方法区所有线程共享。

1. 程序计数器

学过汇编语言的同学肯定不陌生,它保存着当前正在执行的指令的地址。
Question:为什么程序计数器是私有的?
要解答这个问题需要从程序计数器的作用来回答:
(1)字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制。
(2)在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程运行到哪了。
注意,如果当前线程执行的是native方法,则其值为null。
所以,程序计数器私有主要是为了线程切换后能够恢复到正确的执行位置。

2. 本地方法栈

Java底层很多都是由C++实现的,例如我们打开查看Thread类的源码最后就会发现start()方法中调用的start0()就是本地方法。

    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
            
        group.add(this);

        boolean started = false;
        try {
            start0();      // 此处调用了本地方法start0()
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }
	
	// 本地方法start0
    private native void start0();

因此,本地方法栈为虚拟机使用到的native方法服务。

3. 虚拟机栈

虚拟机栈也就是平时所说的栈内存,每个Java方法在被调用的时候都会创建一个栈帧(一个方法就对应着一个栈帧),并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就结束了。
栈帧由四部分组成,分别是局部变量表操作数栈动态链接方法出口
在这里插入图片描述
局部变量表 :存放着方法中的局部变量
操作数栈:用来操作方法中的数的一个临时栈
动态链接:把符号引用存在直接应用存在内存空间中
方法出口:记录该方法调用完毕应该回到的地方

4. 堆

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。在jdk1.8后,字符串从常量池从永久代(方法区)中分离出来,存放于堆中。
在这里插入图片描述
堆的结构如上图所示,这里不做过多讲解。

5. 方法区

方法区在jdk1.7时采用永久代的方式,到了1.8时改成了元数据区,使用本地内存,并且字符串常量池也移入到堆区进行管理。方法区存放着虚拟机加载的类信息常量静态变量以及即时编译器编译的方法代码等。

一个简单的Math运行过程

这里通过一个简单的例子来演示虚拟机栈的使用过程

public class JVM {
    public static void main(String[] args) {
        int a=1;
        int b=2;
        int c=a+b;
        System.out.println(c);
    }
}

上面的代码是一个非常简单的两数相加操作,那么这个过程在JVM内存中是如何进行的呢?

我们可以在target的classes目录下通过javap -c JVM.class这条命令获取字节码的反编译指令,得到的指令如下所示:

public class JVM {
  public JVM();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: iload_3
      12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      15: return
}

这里介绍几个常用的JVM指令集:
iconst_1:int型常量值1进栈
istore_1: 将栈顶int型数值存入第二个局部变量,从0开始计数
iload_1: 第二个int型局部变量进栈,从0开始计数
iadd: 栈顶两int型数值相加,并且结果进栈
注意这里的局部变量是从0开始计数的,因此istore_1和iload_1是第二个局部变量。 第一个局部变量为this,指向该对象。

执行步骤:
1. main线程开始运行,此时局部变量表和操作数栈均为空

在这里插入图片描述

2. 常量值1进入操作数栈:iconst_1(这里1指数字1)

在这里插入图片描述

3. 栈顶元素存入第二个局部变量:istore_1(这里1指第二个局部变量)

在这里插入图片描述

4. 重复上面两步后(iconst_2,istore_2)得到下面结果

在这里插入图片描述

5. 将第2、3个局部变量放入操作数栈(iload_1, iload_2)

注意局部变量是从0开始计数的,这里指第二第三个
在这里插入图片描述

6. 将操作数栈中最上面两个数取出相加后再存放入操作数栈中(iadd)

在这里插入图片描述

7. 最后将操作数栈中的结果存放到第四个局部变量中(istore_3)

在这里插入图片描述
不难发现,JVM指令其实已经很类似于汇编语言了,可以看出越接近底层代码量就会越多。

参考

java-JVM 角度说进程和线程之间的关系
栈帧的描述
JDK1.8 JVM内存模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值