JVM虚拟机(上) -- JVM结构及类的加载

3 篇文章 0 订阅

 

概念

类加载机制

加载器

JVM结构

虚拟机栈

本地方法栈

程序计数器


本节讲涉及JVM虚拟机相关知识点,包括JVM的体系结构,Class的加载.GC等相关知识放在下节.


概念

首先来了解概念,什么是JVM,JVM与JDK,JRE是什么关系.

JRE ===> JRE是java运行环境,是java程序能跑起来的基础

JDK ===> JDK是java开发工具包,JDK中也有一套JRE,所以在jdk的安装路径底下会有jre

JVM ===> JVM即java虚拟机,是JRE的一部分.是虚构出来的计算机,它包括字节码指令集,寄存器,栈,堆和方法区等.

用简单点的图形表示三者关系就是这样

复杂点的话就是这样

 


类加载机制

类加载主要包括加载,验证,准备,解析,初始化几步

而加载器这里就要再说道说道了.

加载器

JDK提供三种加载器从上往下分别是

BootStrap ClassLoader 启动类加载器 ===>加载 jre/lib/*.jar 底下类

Extension ClassLoader 扩展类加载器 ===>加载  jre/lib/ext/*.jar 底下的类

Application ClassLoader 应用程序加载器 ===>加载 classpath底下的类

这三种加载器从上往下依次为下一级的父类

而在加载器的选择上,存在一种机制:双亲委托

双亲委托机制即当加载一个类时,首先判断是否已被加载,如果已经被加载则直接返回,如果未被加载,则直接将加载的任务提交给父类,父类同理,直到最顶层(BootStrap ClassLoader),当所以父类都没有加载的时候,才会调用当前加载类去加载.

双亲委派机制主要是为了防止类的重复加载


JVM结构

接下来说说jvm结构,先看一张简图

 这是一张Class的加载图,虚拟机栈,本地方法栈,程序计数器为线程私有数据,一个线程对应一个.堆,方法区(元空间)为线程共享数据,所以线程都能访问.接下来对这些部分依次分析.

虚拟机栈

每个线程在运行时都会有一个栈与之对应,这点在之前讲锁的升级相关相关知识点时有涉及到.一个线程对应一个,而栈里面可能存在若干栈帧,具体有几个,取决于线程运行时调用的方法.比如

 

从图中可以看到,运行到该断点的时候,main没有执行完,被压在底部,showStack在顶部,这时在该执行线程的中就会分配两个栈帧分别对应这两个方法.而当运行到getError方法时则会抛出异常 java.lang.StackOverflowError.原因在于getError方法是一个无限递归,不停调用自己,这样也就意味着需要不停的创建栈帧,而当线程对应的的空间不足以容纳这么多栈帧时,就会抛出该Erorr.

 而每个栈帧底下又有局部变量表,操作数栈,动态链接,方法返回地址

 局部变量表:

  变量值的存储空间,用于存放方法参数及方法内部的局部变量;局部变量表以变量槽为最小单位(Slot),32位虚拟机中一个Slot可以存放32位(4 字节)以内的数据类型( boolean、byte、char、short、int、float、reference和returnAddress八种,对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写.reference类型虚拟机规范没有明确说明它的长度,但一般来说,虚拟机实现至少都应当能从此引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据.Slot是可以重用的,当Slot中的变量超出了作用域,那么下一次分配Slot的时候,将会覆盖原来的数据.Slot对对象的引用会影响GC(要是被引用,将不会被回收).  系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值).也就是说不存在类变量那样的准备阶段

操作数栈:

  操作数栈和局部变量表一样,在编译时期就已经确定了该方法所需要分配的局部变量表的最大容量.操作数栈的每一个元素可用是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型占用的栈容量为2.当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作(例如:在做算术运算的时候是通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行参数传递的).

动态链接:

  每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中方法的符号引用为参数.这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态方法,私有方法等),这种转化称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接.

方法返回地址:

  用来返回方法的被调用位置.

本地方法栈

登记native方法,在Execution Engine执行时加载本地方法库,java底层是并非是java,本地方法栈则是用来记录这些方法.

程序计数器

指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码).在后文中例子可以看到编码后的文件中执行语句前都存在数字编号,那些编号就是为程序计数器指路的路标,当程序计数器增长到某个数的时候才会知道自己该去执行哪条命令.


例子:

public class MyJvmShow {
    public static void main(String[] args) {
        MyJvmShow myJvmShow=new MyJvmShow();
        System.out.println(myJvmShow.getNum());
    }

    public int getNum(){
        int a=1;
        int b=2;
        int c=(a+b)*100;
        return c;
    }
}

编译后

Classfile /F:/zDemo/studyDemo/spring2Demo/target/classes/study/demo/myJvm/MyJvmShow.class
  Last modified 2020-1-8; size 723 bytes
  MD5 checksum 96d74beeaec9c0360014d938a3af141e
  Compiled from "MyJvmShow.java"
public class study.demo.myJvm.MyJvmShow
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#28         // java/lang/Object."<init>":()V
   #2 = Class              #29            // study/demo/myJvm/MyJvmShow
   #3 = Methodref          #2.#28         // study/demo/myJvm/MyJvmShow."<init>":()V
   #4 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #2.#32         // study/demo/myJvm/MyJvmShow.getNum:()I
   #6 = Methodref          #33.#34        // java/io/PrintStream.println:(I)V
   #7 = Class              #35            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lstudy/demo/myJvm/MyJvmShow;
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               myJvmShow
  #20 = Utf8               getNum
  #21 = Utf8               ()I
  #22 = Utf8               a
  #23 = Utf8               I
  #24 = Utf8               b
  #25 = Utf8               c
  #26 = Utf8               SourceFile
  #27 = Utf8               MyJvmShow.java
  #28 = NameAndType        #8:#9          // "<init>":()V
  #29 = Utf8               study/demo/myJvm/MyJvmShow
  #30 = Class              #36            // java/lang/System
  #31 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;
  #32 = NameAndType        #20:#21        // getNum:()I
  #33 = Class              #39            // java/io/PrintStream
  #34 = NameAndType        #40:#41        // println:(I)V
  #35 = Utf8               java/lang/Object
  #36 = Utf8               java/lang/System
  #37 = Utf8               out
  #38 = Utf8               Ljava/io/PrintStream;
  #39 = Utf8               java/io/PrintStream
  #40 = Utf8               println
  #41 = Utf8               (I)V
{
  public study.demo.myJvm.MyJvmShow();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lstudy/demo/myJvm/MyJvmShow;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class study/demo/myJvm/MyJvmShow
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: aload_1
        12: invokevirtual #5                  // Method getNum:()I
        15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        18: return
      LineNumberTable:
        line 8: 0
        line 9: 8
        line 10: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  args   [Ljava/lang/String;
            8      11     1 myJvmShow   Lstudy/demo/myJvm/MyJvmShow;

  public int getNum();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: bipush        100
         9: imul
        10: istore_3
        11: iload_3
        12: ireturn
      LineNumberTable:
        line 13: 0
        line 14: 2
        line 15: 4
        line 16: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   Lstudy/demo/myJvm/MyJvmShow;
            2      11     1     a   I
            4       9     2     b   I
           11       2     3     c   I
}
SourceFile: "MyJvmShow.java"

 相关部分可自行对应参考.

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值