通过java程序运行过程来了解JVM原理

今天这篇文章我们具体分析一下java程序运行起来之中所包含的具体细节,通过这些细节带我们窥探JVM实现的原理。

一、前端编译

使用javac命令将.java程序编译为JVM可解析的.class字节码文件,java作为一种解释型编程语言(当然这么说带有片面性,jvm自带的即时编译器同样可以将字节码直接编译为机器码运行,但是这里我们就不分析即时编译器是如何运行的了),负责将字节码翻译为机器码运行,所以对于字节码内容的理解对了解JVM的原理非常关键。

二、class字节码

可以通过javap -v classfile将class字节码文件翻译为人类可以看懂的形式显示。字节码文件包含了魔数,jdk版本号,常量池,方法指令信息等关于类信息的所有数据,其中常量池会在此类被加载的时候加载到方法区的运行时常量池中

三、类的加载

类加载器找到类的字节码文件,将字节码中类的元数据(接口,类,方法信息等)存储到JVM方法区中,将常量池存储到方法区中的运行时常量池中,并在堆中创建一个Class<T>对象。运行时常量池中的符号引用将会在类加载的解析阶段转换为直接引用,直接执行此类中的方法、类变量、成员变量所在的内存地址。 

第二幅图应该为类加载过程中的初始化时机

 

下面根据一段代码,直观的分析一下具体细节,下面有一段代码

public class JVM {
	//基本数据类型 int类型-127-128默认是在常量池中
	private static final int N1 = 2019;
	private static int N2 = 2020;
	private final int N3 = 2021;
	private int N4 = 2022;
	
	//包装类型
	private static final Integer T1 = 2019;
	private static Integer T2 = 2020;
	private final Integer T3 = 2021;
	private Integer T4 = 2022;
	
	//引用类型
	private static final Demo demo1 = new Demo();
	private static Demo demo2 = new Demo();
	private final Demo demo3 = new Demo();
	private Demo demo4 = new Demo();
	
	public static void main(String[] args) {
        testDemo();
		final int aaa = 8080;
		int bbb = 9090;
		System.out.println(aaa);
		System.out.println(bbb);
	}
	
	public static void testDemo() {
		Demo tempDemo = new Demo();
		tempDemo.test();
	}
}

使用javap -v查看编译后的字节码如下

Classfile /C:/Users/sky/Desktop/JVM.class
  Last modified 2019-11-5; size 1112 bytes
  MD5 checksum 8d2f6171cd7dcdd87e97078a1572e1b5
  Compiled from "JVM.java"
public class JVM
  SourceFile: "JVM.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
//此部分为常量池信息,类加载时加载到方法区中的运行时常量池内
//常量池包含了被final修饰的基本数据类型的字面量2019,2021和字符串字面量
//包含了方法,字段的字符引用,在类加载的解析阶段转换为直接指向其内存地址的直接引用
Constant pool:
   #1 = Methodref          #20.#49        //  java/lang/Object."<init>":()V
   #2 = Fieldref           #19.#50        //  JVM.N3:I
   #3 = Fieldref           #19.#51        //  JVM.N4:I
   #4 = Methodref          #52.#53        //  java/lang/Integer.valueOf:(I)Ljava
/lang/Integer;
   #5 = Fieldref           #19.#54        //  JVM.T3:Ljava/lang/Integer;
   #6 = Fieldref           #19.#55        //  JVM.T4:Ljava/lang/Integer;
   #7 = Class              #56            //  Demo
   #8 = Methodref          #7.#49         //  Demo."<init>":()V
   #9 = Fieldref           #19.#57        //  JVM.demo3:LDemo;
  #10 = Fieldref           #19.#58        //  JVM.demo4:LDemo;
  #11 = Fieldref           #59.#60        //  java/lang/System.out:Ljava/io/Prin
tStream;
  #12 = Methodref          #61.#62        //  java/io/PrintStream.println:(I)V
  #13 = Methodref          #7.#63         //  Demo.test:()V
  #14 = Fieldref           #19.#64        //  JVM.N2:I
  #15 = Fieldref           #19.#65        //  JVM.T1:Ljava/lang/Integer;
  #16 = Fieldref           #19.#66        //  JVM.T2:Ljava/lang/Integer;
  #17 = Fieldref           #19.#67        //  JVM.demo1:LDemo;
  #18 = Fieldref           #19.#68        //  JVM.demo2:LDemo;
  #19 = Class              #69            //  JVM
  #20 = Class              #70            //  java/lang/Object
  #21 = Utf8               N1
  #22 = Utf8               I
  #23 = Utf8               ConstantValue
  #24 = Integer            2019
  #25 = Utf8               N2
  #26 = Utf8               N3
  #27 = Integer            2021
  #28 = Utf8               N4
  #29 = Utf8               T1
  #30 = Utf8               Ljava/lang/Integer;
  #31 = Utf8               T2
  #32 = Utf8               T3
  #33 = Utf8               T4
  #34 = Utf8               demo1
  #35 = Utf8               LDemo;
  #36 = Utf8               demo2
  #37 = Utf8               demo3
  #38 = Utf8               demo4
  #39 = Utf8               <init>
  #40 = Utf8               ()V
  #41 = Utf8               Code
  #42 = Utf8               LineNumberTable
  #43 = Utf8               main
  #44 = Utf8               ([Ljava/lang/String;)V
  #45 = Utf8               testDemo
  #46 = Utf8               <clinit>
  #47 = Utf8               SourceFile
  #48 = Utf8               JVM.java
  #49 = NameAndType        #39:#40        //  "<init>":()V
  #50 = NameAndType        #26:#22        //  N3:I
  #51 = NameAndType        #28:#22        //  N4:I
  #52 = Class              #71            //  java/lang/Integer
  #53 = NameAndType        #72:#73        //  valueOf:(I)Ljava/lang/Integer;
  #54 = NameAndType        #32:#30        //  T3:Ljava/lang/Integer;
  #55 = NameAndType        #33:#30        //  T4:Ljava/lang/Integer;
  #56 = Utf8               Demo
  #57 = NameAndType        #37:#35        //  demo3:LDemo;
  #58 = NameAndType        #38:#35        //  demo4:LDemo;
  #59 = Class              #74            //  java/lang/System
  #60 = NameAndType        #75:#76        //  out:Ljava/io/PrintStream;
  #61 = Class              #77            //  java/io/PrintStream
  #62 = NameAndType        #78:#79        //  println:(I)V
  #63 = NameAndType        #80:#40        //  test:()V
  #64 = NameAndType        #25:#22        //  N2:I
  #65 = NameAndType        #29:#30        //  T1:Ljava/lang/Integer;
  #66 = NameAndType        #31:#30        //  T2:Ljava/lang/Integer;
  #67 = NameAndType        #34:#35        //  demo1:LDemo;
  #68 = NameAndType        #36:#35        //  demo2:LDemo;
  #69 = Utf8               JVM
  #70 = Utf8               java/lang/Object
  #71 = Utf8               java/lang/Integer
  #72 = Utf8               valueOf
  #73 = Utf8               (I)Ljava/lang/Integer;
  #74 = Utf8               java/lang/System
  #75 = Utf8               out
  #76 = Utf8               Ljava/io/PrintStream;
  #77 = Utf8               java/io/PrintStream
  #78 = Utf8               println
  #79 = Utf8               (I)V
  #80 = Utf8               test
{
  public JVM();
    flags: ACC_PUBLIC
    Code:
      //对象构造方法 会为成员变量赋值
      //stack=3 操作数栈深为3, locals=1 局部变量个数, args_size=1 参数个数
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: aload_0
         5: sipush        2021
         8: putfield      #2                  // Field N3:I
        11: aload_0
        12: sipush        2022
        15: putfield      #3                  // Field N4:I
        18: aload_0
        19: sipush        2021
        22: invokestatic  #4                  // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        25: putfield      #5                  // Field T3:Ljava/lang/Integer;
        28: aload_0
        29: sipush        2022
        32: invokestatic  #4                  // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        35: putfield      #6                  // Field T4:Ljava/lang/Integer;
        38: aload_0
        39: new           #7                  // class Demo
        42: dup
        43: invokespecial #8                  // Method Demo."<init>":()V
        46: putfield      #9                  // Field demo3:LDemo;
        49: aload_0
        50: new           #7                  // class Demo
        53: dup
        54: invokespecial #8                  // Method Demo."<init>":()V
        57: putfield      #10                 // Field demo4:LDemo;
        60: return
      LineNumberTable:
        line 1: 0
        line 5: 4
        line 6: 11
        line 11: 18
        line 12: 28
        line 17: 38
        line 18: 49

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: sipush        9090
         3: istore_2
         4: getstatic     #11                 // Field java/lang/System.out:Ljav
a/io/PrintStream;
         7: sipush        8080
        10: invokevirtual #12                 // Method java/io/PrintStream.prin
tln:(I)V
        13: getstatic     #11                 // Field java/lang/System.out:Ljav
a/io/PrintStream;
        16: iload_2
        17: invokevirtual #12                 // Method java/io/PrintStream.prin
tln:(I)V
        20: return
      LineNumberTable:
        line 22: 0
        line 23: 4
        line 24: 13
        line 25: 20

  public static void testDemo();
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: new           #7                  // class Demo
         3: dup
         4: invokespecial #8                  // Method Demo."<init>":()V
         7: astore_0
         8: aload_0
         9: invokevirtual #13                 // Method Demo.test:()V
        12: return
      LineNumberTable:
        line 28: 0
        line 29: 8
        line 30: 12

    
  static {}; //所有的没有被final修饰的或者被final修饰的引用类型的类变量被赋值,会在类加载的初始 
             //化阶段执行
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: sipush        2020
         3: putstatic     #14                 // Field N2:I
         6: sipush        2019
         9: invokestatic  #4                  // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        12: putstatic     #15                 // Field T1:Ljava/lang/Integer;
        15: sipush        2020
        18: invokestatic  #4                  // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        21: putstatic     #16                 // Field T2:Ljava/lang/Integer;
        24: new           #7                  // class Demo
        27: dup
        28: invokespecial #8                  // Method Demo."<init>":()V
        31: putstatic     #17                 // Field demo1:LDemo;
        34: new           #7                  // class Demo
        37: dup
        38: invokespecial #8                  // Method Demo."<init>":()V
        41: putstatic     #18                 // Field demo2:LDemo;
        44: return
      LineNumberTable:
        line 4: 0
        line 9: 6
        line 10: 15
        line 15: 24
        line 16: 34
}

对于上面代码运行的总结:

1.变量的初始化

  • 类变量(被static修饰的变量)和同时被static和final修饰的引用类型会在类构造方法<cinit>()中中初始化,初始化操作是在类加载过程中的初始化阶段,static代码块也是在类构造方法<cinit>()中执行
  • 同时被static和final修饰的基础数据类型和string类型会在类加载过程中的准备阶段使用常量池中的值对其初始化
  • 非静态成员变量(无static修饰)会在对象构造方法中初始化

2.方法的执行

    1类加载器将JVM.class加载到方法区,并且会将运行时常量池中的符号引用转换为直接引用

    2通过main方法的直接引用找到了main方法内存地址,jvm可以解析main方法的字节码指令来执行方法之中的操作

    3通过运行时常量池找到testDemo方法的直接引用,执行testDemo的字节码指令

    4.查看Demo类有没有在运行时常量池中加载,若没有加载Demo.class字节码,将字节码加载到运行时常量区,并将Demo类    的运行时常量池中的符号引用转化为直接引用

    5将JVM类的常量池中的Demo类的符号引用转换为直接引用,这样可以直接找到Demo类了

    6在堆中给demo对象分配空间,并且demo对象中持有Demo类的运行时常量池

    7使用在demo对象中常量池引用找到Demo类常量池,在常量池中找到test()方法的直接应用,执行test方法的字节码指令

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值