初探JVM原理与结构:虚拟机的3个子系统,运行时内存区,堆、栈、方法区概念

初探JVM原理与结构

————————————————
版权声明:本文为CSDN博主「Deegue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyzzxycj/article/details/89848442

前言

这篇文章是JVM的扫盲篇,通俗易懂,对不清楚

  • ClassLoader
  • Garbage Collection,
  • 堆(heap)、
  • 栈(stack)和
  • 方法区(method)

之间的关系,可以有个大致的了解。

Garbage
英
/ˈɡɑːbɪdʒ/
n.
<美>垃圾,废物;<美>垃圾箱;<非正式>废话,无聊的东西;无用信息

HelloWorld 及流程

在还是小白阶段的时候,我们都会写一个HelloWorld.java,然后执行javac 获得HelloWorld.class

public class HelloWorld {
 
	public static void main(String[] args) {
		System.out.println("HelloWorld");
	}
}

img

在执行java HelloWorld的时候,

  • Java命令首先启动虚拟机进程
  • 虚拟机进程成功启动后,读取参数“HelloWorld”,把他作为初始类加载到内存,对这个类进行初始化和动态链接
  • 然后从这个类的main方法开始执行。

也就是说我们的.class文件不是直接被系统加载后直接在cpu上执行的,而是被一个叫做虚拟机的进程托管的。

  • 首先必须虚拟机进程启动就绪,
  • 然后由虚拟机中的类加载器加载必要的class文件,包括jdk中的基础类(如String和Object等),
  • 然后由虚拟机进程解释class字节码指令,把这些字节码指令**翻译成本机cpu能够识别的指令,**才能在cpu上运行。

img

根据上图表达的内容,我们编译之后的

class文件是作为Java虚拟机的原料被输入到Java虚拟机的内部的

那么具体由谁来做这一部分工作呢?其实在Java虚拟机内部,有一个叫做类加载器的子系统,这个子系统用来在运行时根据需要加载类。

注意上面一句话中的“根据需要”四个字。在Java虚拟机执行过程中,只有他需要一个类的时候,才会调用类加载器来加载这个类,并不会在开始运行时加载所有的类。就像一个人,只有饿的时候才去吃饭,而不是一次把一年的饭都吃到肚子里。一般来说,

  • 虚拟机加载类的时机,在第一次使用一个新的类的时候。

从这个层面上来看,在执行一个所谓的java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程,而不是我们写的一个个的class文件。这个叫做虚拟机的进程处理一些底层的操作,比如内存的分配和释放等等。

我们编写的class文件只是虚拟机进程执行时需要的“原料”。这些“原料”在运行时被加载到虚拟机中,被虚拟机解释执行,以控制虚拟机实现我们java代码中所定义的一些相对高层的操作,比如创建一个文件等,

  • 可以将class文件中的信息看做对虚拟机的控制信息,也就是一种虚拟指令

我们知道,Java虚拟机会进行自动内存管理。具体说来就是自动释放没有用的对象,而不需要程序员编写代码来释放分配的内存。这部分工作由垃圾收集子系统负责

Java虚拟机 三个子系统

从上面的论述可以知道, 一个Java虚拟机实例在运行过程中有三个子系统来保障它的正常运行,分别是

  • 类加载器子系统,
  • 执行引擎子系统和
  • 垃圾收集子系统。

如下图所示:

img

虚拟机的运行,必须加载class文件,并且执行class文件中的字节码指令。它做这么多事情,必须需要自己的空间。就像人吃下去的东西首先要放在胃中。虚拟机也需要空间来存放个中数据。首先,

  • 加载的字节码,需要一个单独的内存空间来存放;
  • 一个线程的执行,也需要内存空间来维护方法的调用关系,存放方法中的数据和中间计算结果;在执行的过程中,无法避免的要创建对象
  • 创建的对象需要一个专门的内存空间来存放。

运行时内存区

虚拟机的运行时内存区大概可以分成下图所示的几个部分:

  • Class 文件:方法区
  • 线程执行数据:栈
  • 对象存储区:堆

img

  • 复杂的划分

img

运行时数据区

  • Java栈
  • 本地方法栈
    • 新生代、老年代和永久代。
      • 新生代:8:1:1 Eden, Survivor1, Survivor2 幸存1区
  • 方法区
  • 程序计数器

堆、栈、方法区概念

堆区:

  1. 存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
  2. jvm只有一个堆区(heap),被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

栈区:

  1. 每个线程包含一个栈区,栈中只保存基础数据类型的对象的引用自定义对象的引用(不是对象),对象都存放在堆区中。

  2. 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。

  3. 栈分为3个部分:

    1. 基本类型变量区
    2. 执行环境上下文
    3. 操作指令区(存放操作指令)。

方法区:

  1. 又叫静态区,跟堆一样,被所有的线程共享。

    1. 方法区包含所有的class和static变量。
  2. 方法区中包含的都是在整个程序中永远唯一的元素

    1. 如class,static变量

具体解释

为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。
AppMain.java

    //运行时, jvm 把appmain的信息都放入方法区
    public class AppMain {
        
        //main 方法本身放入方法区。
        public static void main(String[] args) {
            
            //test1是引用,所以放到栈区里, 
            //Sample是自定义对象应该放到堆里面
            Sample test1 = new Sample(" 测试1 ");
            Sample test2 = new Sample(" 测试2 ");
            test1.printName();
            test2.printName();
        }
    }

Sample.java

	//运行时, jvm 把appmain的信息都放入方法区
    public class Sample {
        
        /**
         * 范例名称
         * new Sample实例后, name 引用放入栈区里,  name 对象放入堆里
         */
        private name;      

        /**
         * 构造方法
         */
        public Sample(String name) {
            this.name = name;
        }

        /**
         * 输出
         * print方法本身放入 方法区里。
         */
        public void printName() {
            System.out.println(name);
        }
    }

图解和总结

img

系统收到了我们发出的指令,

  • 启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据
  • 然后把Appmain类的 类信息存 放到 运行时数据区的方法区中。这一过程称为AppMain类的加载过程。
  • 接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:
Sample test1=new Sample(“测试1);

语句很简单啦,就是让java虚拟机创建一个Sample实例,并且呢,使引用变量test1引用这个实例。貌似小case一桩哦,就让我们来跟踪一下Java虚拟机,看看它究竟是怎么来执行这个任务的:

1、 Java虚拟机一看,不就是建立一个Sample实例吗,简单,于是就直奔方法区而去,先找到Sample类的类型信息再说。结果呢,嘿嘿,没找到@@,这会儿的方法区里还没有Sample类呢。

  • 可Java虚拟机也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类,把Sample类的类型信息存放在方法区里。

2、 好啦,资料找到了,下面就开始干活啦。Java虚拟机做的第一件事情就是在堆区中为一个新的Sample实例分配内存,

  • 这个Sample实例 持有着指向方法区的 Sample类 的类型信息的引用。这里所说的引用,实际上指的是Sample类的类型信息 在方法区中的 内存地址,其实,就是有点类似于C语言里的指针啦~~,而这个地址呢,就存放了在Sample实例的数据区里。

3、 在JAVA虚拟机进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!

  • 位于“=”前的Test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中
  • 而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用

OK,到这里为止呢,JAVA虚拟机就完成了这个简单语句的执行任务。参考我们的行动向导图,我们终于初步摸清了JAVA虚拟机的一点点底细了,COOL!

接下来,JAVA虚拟机将继续执行后续指令,

  • 在堆区里继续创建另一个Sample实例,然后依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,
    • JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,
    • 再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,
    • 从而获得printName()方法的字节码,接着执行printName()方法包含的指令。

总结:

  • 虚拟机并不神秘,在操作系统的角度看来,它只是一个普通进程。
  • 这个叫做虚拟机的进程比较特殊,它能够加载我们编写的class文件。如果把JVM比作一个人,那么class文件就是我们吃的食物。
  • 加载class文件的是一个叫做类加载器的子系统。就好比我们的嘴巴,把食物吃到肚子里。
  • 虚拟机中的执行引擎用来执行class文件中的字节码指令。就好比我们的肠胃,对吃进去的食物进行消化。
  • 虚拟机在执行过程中,要分配内存创建对象。当这些对象过时无用了,必须要自动清理这些无用的对象。
    • 清理对象回收内存的任务由垃圾收集器负责。就好比人吃进去的食物,在消化之后,必须把废物排出体外,腾出空间可以在下次饿的时候吃饭并消化食物。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值