JVM独家学习(2) JVM有哪些内存区域,分别用来做什么的

JVM加载回顾

上次我解释了JVM是如何加载class的,以及有哪些加载器。请看下图我们在进行一次简单的回忆!

一个类从加载到使用,一般会经历下面的这个过程 看图:

加载->验证->准备->初始化->使用->卸载销毁
在这里插入图片描述

我们写的字节码如何运行的

在这里插入图片描述

我们写的代码被编译成字节码,实际上还是java虚拟机调用执行引擎来执行的。这里我就不详细说了,可以看我以前写的文章。

JVM到底是怎么划分内存区域的

1,首先我们写的java代码是不是要被加载到JVM进行运行,我给大家画了一个整体的架构图。
在这里插入图片描述
通过图解我们知道java代码被编译成了class,然后由我们的加载器加载到jvm,然后由java执行器执行编译好的字节码,在程序运行的时期就会用到数据和相关的信息,这个时候就需要java运行时区域,这个区域也是我们通常说的jvm内存,内存的管理也是对这个区域进行管理,比如分配内存和回收内存。
java运行时区域图解:

在这里插入图片描述
这几个区域我不想像别人那样写一大堆文字,然后你们看了一点都不知道是什么东西。这个很难理解,接下来我将结合一些代码来讲解。

我们思考一个问题,我们假设我们的代码要存储哪些东西在运行时区域里面呢?我大致归纳了一下,看是否和你们想的一样哦。
在这里插入图片描述
首先我们写的所有的class是不是都要有一个地方存储啊?我们代码在运行的时候是不是要执行我们一个个的方法?在运行某个方法的时候,如果要创建对象是不是要有一个存放对象的地方啊?你有这些疑问的时候设计的人同样是这样的!

这个就是JVM为什么要划分不同的区域来存放了,它是为了我们写好代码在运行的过程中根据需要来使用。

存放我们类的方法区

JDK1.8以前叫做方法区,我现在用的是1.8的JDK,JDK8把这块区域叫做“元数据空间”。这个就专门存放我们写的各种类的信息。
结合我们代码来看看

package cn.changhong.conf.client.utils;

/**
 * @author 独秀天狼
 */
public class Luancher {

    public static void main(String[] args) {
        Message message=new Message();
        SendMsg sender=new DingdingSendMsg();
        sender.send(message);
    }
}

根据我原来说的只要用到了class就会加载到JVM看图理解比较好!
在这里插入图片描述

存放执行指令的程序计数器


/**
 * @author 独秀天狼
 */
public class Luancher {

    public static void main(String[] args) {
        Message message=new Message();
        SendMsg sender=new DingdingSendMsg();
        sender.send(message);
    }
}

当前我们这个代码会被编译成字节码,字节码就是计算器可以理解的语言,我们字节码的执行指令大概是如下,给大家看一下:
想看到代码的一些指令,可以用javap 来看我们的class转换到指令是什么样的。

public class cn.changhong.conf.client.utils.Luancher {
  public cn.changhong.conf.client.utils.Luancher();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class cn/changhong/conf/client/utils/Message
       3: dup
       4: invokespecial #3                  // Method cn/changhong/conf/client/utils/Message."<init>":()V
       7: astore_1
       8: new           #4                  // class cn/changhong/conf/client/utils/DingdingSendMsg
      11: dup
      12: invokespecial #5                  // Method cn/changhong/conf/client/utils/DingdingSendMsg."<init>":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokeinterface #6,  2            // InterfaceMethod cn/changhong/conf/client/utils/SendMsg.send:(Lcn/changhong/conf/client/utils/Message;)V
      23: return
}

E:\apollo_conf\target\classes\cn\changhong\conf\client\utils>

比如“0: aload_0”就是字节码指令,计算机读到这条指令就知道要做什么了。
所以在这里大家要明白:我们写的代码最终会被转换成字节码指令。
在这里插入图片描述
在字节码执行的时候就需要一个内存区域来记录我们执行指令的位置。如图
在这里插入图片描述
我们的程序基本上都是多线程执行指令的,每个线程执行指令都会有一个独立的计数器,专门记录当前的线程执行指令到那个位置了。如图:
在这里插入图片描述
通过这样的图解是不是比较清晰了。

Java虚拟机栈

当java代码在执行的时候,一定是线程执行某个方法,比如下面我写出的这些代码,这个会有一个main线程去执行main方法的代码,当线程main执行main方法的时候,就会有对于的程序计数器来记录当前main线程执行的指令位置。

/**
 * @author 独秀天狼
 */
public class Luancher {

    public static void main(String[] args) {
        Message message=new Message();
        SendMsg sender=new DingdingSendMsg();
        sender.send(message);
    }
}

方法里面我们会有一些局部的成员变量,就当前这个代码就有message,sender因此我们JVM就必须要有一个区域来保存当前的局部变量等数据。这个就是java虚拟机栈。
每个线程都有自己的虚拟机栈,这个代码里面就会有main线程的虚拟机栈,用来存放自己执行的局部变量。当执行方法就会创建一个栈帧
这个栈帧就存储了局部变量,操作数栈,方法出口等,这个时候就会把main方法压入到main线程创建的虚拟机栈中,同时 message ,sender就会依次存放。如图所示!
在这里插入图片描述
紧接着 sender.send()方法就来到了 DingdingSendMsg 里面的这个send方法,这个方法里面也有局部变量


public class DingdingSendMsg implements SendMsg {
    @Override
    public void send(Message message) {
        String title=message.getTitle();
        System.out.println(title);
        //TODO 发送钉钉消息
    }
}

那这个时候也会为send方法创建一个栈帧,压入到线程的虚拟机栈里面。如图:
在这里插入图片描述

上述就是JVM中的java虚拟机栈整个图解过程,调用任何方法都会为这个方法创建一个栈帧然后入栈,这个栈帧里面就保存了局部变量的数据,和方法执行的相关的信息,执行完了就出栈。
最后我们把整个过程图解一下:
在这里插入图片描述
以上就java虚拟机栈的整个图解,希望大家已经明白了。

java对象的存储 堆

我们现在已经知道了main线程执行main()方法,会有自己的程序计数器,还会把main方法的局部变量依次压入java虚拟机栈中存放。

在上面的图中还有一个非常关键的区域没有说明,那就是java堆内存,这个就是存放我们代码创建的各种对象的。
比如刚刚那些代码里面:

/**
 * @author 独秀天狼
 */
public class Luancher {

    public static void main(String[] args) {
        Message message=new Message();
        SendMsg sender=new DingdingSendMsg();
        sender.send(message);
    }
}

这个代码里面就有 new Message()和 new DingdingSendMsg()这2个对象创建,所以我们创建的这部分数据就会存储在堆中。

然后局部变量 message,sender引用了对象 Mesage,DingdingSendMsg,我用一个图来说明。
在这里插入图片描述

总结

最后我将画一个完整的图给大家体会一下,这样就更加的清晰和明了。
在这里插入图片描述

谢谢大家的阅读,希望能给你们梳理清楚了这个难懂的JVM内存结构。面试再也不怕了,也真正的理解了,java工作的原理了。

喜欢就给我点赞。
github代码案例 后续有大量的springboot代码案例

本文中版权归独秀天狼团队所有,转载请标注清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章