资讯详情

jvm学习笔记(二)

Jvm学习笔记(2)

类加载

类文件结构

ClassFile{ 
             u4 magic;    //表示是否class类型的文件 cafe babe     u2 minor_version;  //十六进制表示 数组数组和jdk版本有对应关系     u2 major_version;     u2 constant_pool_count;//常量池     cp_info constant_pool[constant_pool_count - 1];     u2 access_flags;     u2 this_class;     u2 super_class;     u2 interfaces_count;     u2 interfaces[interfaces_count];     u2 fields_count;     field_info fields[fields_count];     u2 methods_count;     method_info methods[methods_count];     u2 attributes_count;     attrbute_info attrbutes[attrbutes_count];      } 
常量池
类型 标志(或标志) 描述
CONSTANT_utf8_info 1 UTF-8编码字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点字面量
CONSTANT_Class_info 7 引用类或接口符号
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 引用字段符号
CONSTANT_Methodref_info 10 引用类法中的符号
CONSTANT_InterfaceMethodref_info 11 引用接口中方法的符号
CONSTANT_NameAndType_info 12 引用字段或方法的符号
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示动态方法调用点

在解释这些常量之前,我们应该找出几个概念。 常量池主要储存两类常量:字面量(Literal)符号引用(Symbolic Refrences)。如下表:

常量 具体的常量
字面量 文本字符串
声明为final的常量值
符号引用 类别和接口的全限定名
字段的名称和描述符
方法的名称和描述符

字节码指令

javap工具

Oracle提供了javap反编译工具class文件

javap -v HelloWorld.class 

  Last modified 2022-7-8; size 559 bytes
  MD5 checksum 5053c7c4ad0a79418343e5c4e0e97476
  Compiled from "HelloWorld.java"
public class com.gao.demo.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref #6.#20 // java/lang/Object."<init>":()V
   #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String #23 // hello world
   #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class #26 // com/gao/demo/HelloWorld
   #6 = Class #27 // java/lang/Object
   #7 = Utf8 <init>
   #8 = Utf8 ()V
   #9 = Utf8 Code
  #10 = Utf8 LineNumberTable
  #11 = Utf8 LocalVariableTable
  #12 = Utf8 this
  #13 = Utf8 Lcom/gao/demo/HelloWorld;
  #14 = Utf8 main
  #15 = Utf8 ([Ljava/lang/String;)V
  #16 = Utf8 args
  #17 = Utf8 [Ljava/lang/String;
  #18 = Utf8 SourceFile
  #19 = Utf8 HelloWorld.java
  #20 = NameAndType #7:#8 // "<init>":()V
  #21 = Class #28 // java/lang/System
  #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
  #23 = Utf8 hello world
  #24 = Class #31 // java/io/PrintStream
  #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
  #26 = Utf8 com/gao/demo/HelloWorld
  #27 = Utf8 java/lang/Object
  #28 = Utf8 java/lang/System
  #29 = Utf8 out
  #30 = Utf8 Ljava/io/PrintStream;
  #31 = Utf8 java/io/PrintStream
  #32 = Utf8 println
  #33 = Utf8 (Ljava/lang/String;)V
{ 
        
  public com.gao.demo.HelloWorld();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/gao/demo/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3 // String hello world
         5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

图解运行流程

public class Demo { 
        

    public static void main(String[] args) { 
        
        int a = 10;   
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
    }
}

  Last modified 2022-7-8; size 475 bytes
  MD5 checksum 19b70530830a66824666ba8fd66fdc33
  Compiled from "Demo.java"
public class com.gao.demo.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref #5.#23 // java/lang/Object."<init>":()V
   #2 = Class #24 // java/lang/Short
   #3 = Integer 32768
   #4 = Class #25 // com/gao/demo/Demo
   #5 = Class #26 // java/lang/Object
   #6 = Utf8 <init>
   #7 = Utf8 ()V
   #8 = Utf8 Code
   #9 = Utf8 LineNumberTable
  #10 = Utf8 LocalVariableTable
  #11 = Utf8 this
  #12 = Utf8 Lcom/gao/demo/Demo;
  #13 = Utf8 main
  #14 = Utf8 ([Ljava/lang/String;)V
  #15 = Utf8 args
  #16 = Utf8 [Ljava/lang/String;
  #17 = Utf8 a
  #18 = Utf8 I
  #19 = Utf8 b
  #20 = Utf8 c
  #21 = Utf8 SourceFile
  #22 = Utf8 Demo.java
  #23 = NameAndType #6:#7 // "<init>":()V
  #24 = Utf8 java/lang/Short
  #25 = Utf8 com/gao/demo/Demo
  #26 = Utf8 java/lang/Object
{ 
        
  public com.gao.demo.Demo();
    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 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/gao/demo/Demo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: ldc           #3 // int 32768
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return
      LineNumberTable:
        line 8: 0
        line 9: 3
        line 10: 6
        line 11: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1     a   I
            6       5     2     b   I
           10       1     3     c   I
}
SourceFile: "Demo.java"

类加载器会将main方法所在类进行类加载的操作,将字节数据读取到内存里来。

运行时常量池是方法区的一部分

比较小的数字不存放在常量池中,比如int a = 10,如果数字的范围超过了整数的最大值,就会存放在常量池中。

在这里插入图片描述

将方法区字节码放入方法区后,main线程开始运行,分配栈帧。

浅绿色代表局部变量表,蓝色代表操作数栈。

(stack = 2,locals = 4) 对应操作数栈有2个空间(每个空间4个字节),局部变量表中有4个槽位

bipush 10

  • 将一个byte压入操作数栈(其长度会补齐四个字节),类似的命令还有sipush(将short压入操作数栈),ldc(将一个int压入操作数栈),ldc2_w,(将一个long压入操作数栈,分两次压入,因为long是8字节)

istore 1 将操作数栈的数弹出,存入局部变量表中,1代表。此时a就被赋予了10。

ldc #3

  • 从常量池加载#3数据到操作数栈
  • short.max_value是32767,所以32768 = short.Max + 1,这是在编译过程中就准备好的 istore2(局部变量表中的二号槽位)

给b赋值 iload1,load2

读取局部变量表1和2的数到操作数栈 iadd

弹出操作数栈的两个数,将结果存入操作数栈。

istore3

给c赋值(局部变量表中的三号槽位)

getstatic #4 invokevirtual #5

  • 找到常量池 #5项
  • 定位到方法区java/io/PrintStream.println(I) v方法
  • 生成新的栈帧(分配locals,stack等)
  • 传递参数,执行新栈帧中的字节码

控制指令

  • byte,short,char 都会按int比较,因为操作数栈都是4字节
  • goto 用来进行跳转到指定行号的字节码

// 从字节码角度来分析:条件判断指令
public class T04_ByteAnalyseIf { 
        
    public static void main(String[] args) { 
        
        int a = 0;
        if (a == 0) { 
        
            a = 10;
        } else { 
        
            a = 20;
        }
    }
}

字节码:使用javap -v T04_ByteAnalyseIf.class,将java程序对应的字节码如下,并做了执行的注释。

     0: iconst_0            // int型常量值0进操作数栈
     1: istore_1            // 从操作数栈弹出数据存储局部变量表1号槽位
     2: iload_1             // 从局部变量表1号槽位中加载数据到操作数栈中
     3: ifne          12    // 当栈顶int型数值不等于0时跳转到12行
     6: bipush        10    // 将一个byte型常量值10 推送至栈顶
     8: istore_1            // 将栈顶int型数值存入第二个局部变量,从0开始计数
     9: goto          15    // 跳转到15行
    12: bipush        20    // 将一个byte型常量值20 推送至栈顶
    14: istore_1            // 将栈顶int型数值存入第二个局部变量,从0开始计数
    15: return              // 当前方法返回void         

思考:以上比较指令中没有long, float, double 的比较,那么它们要比较怎么办?

其实循环控制还是前面介绍的那些指令,例如while循环:

// 从字节码角度来分析:循环控制指令

public class T05_ByteAnalyseWhile { 
        

    public static void main(String[] args) { 
        
        int a = 0;
        while (a < 10) { 
        
            a++;
        }
    }
}

T05_ByteAnalyseWhile 字节码:使用javap -v T05_ByteAnalyseWhile.class,将java程序对应的字节码如下,并做了执行的注释。

     0: iconst_0            // int型常量值0进栈
     1: istore_1            // 将栈顶int型数值存入第二个局部变量,从0开始计数
     2: iload_1             // 第二个int型局部变量进栈,从0开始计数
     3: bipush        10    // 将一个byte型常量值推送至栈顶
     5: if_icmpge     14    // 比较栈顶两int型数值大小,当结果大于等于0时跳转到14行
     8: iinc          1, 1  // 指定int型变量增加指定值,即自增1
    11: goto          2     // 无条件跳转
    14: return              // 当前方法返回void

上述是从字节码角度分析while,下面是从字节码角度分析do while:

// 从字节码角度来分析:循环控制do while指令

public class T06_ByteAnalyseDoWhile { 
        
    public static void main(String[] args) { 
        
        int a = 0;
        do { 
        
            a++;
        } while (a < 10);
    }
}

T06_ByteAnalyseDoWhile 字节码:使用javap -v T06_ByteAnalyseDoWhile.class,将java程序对应的字节码如下,并做了执行的注释。

     0: iconst_0            // int型常量值0进栈
     1: istore_1            // 将栈顶int型数值存入第二个局部变量,从0开始计数
     2: iinc          1, 1  // 指定int型变量增加指定值,即自增1
     5: iload_1             // 第二个int型局部变量进栈,从0开始计数
     6: bipush        10    // 将一个byte型常量值推送至栈顶
     8: if_icmplt     2     // 比较栈顶两int型数值大小,当结果小于0时跳转
    11: return              // 当前方法返回void

上述是从字节码角度分析do while,下面是从字节码角度分析 for 循环:

// 从字节码角度来分析:循环控制 for 指令
public class T07_ByteAnalyseFor { 
        
    public static void main(String[] args) { 
        
        for (int i = 0; i < 10; i++) { 
        

        }
    }
}

T07_ByteAnalyseFor 字节码:使用javap -v T07_ByteAnalyseFor.class,将java程序对应的字节码如下,并做了执行的注释。

     0: iconst_0            // int型常量值0进栈
     1: istore_1            // 将栈顶int型数值存入第二个局部变量,从0开始计数
     2: iload_1             // 第二个int型局部变量进栈,从0开始计数
     3: bipush        10    // 将一个byte型常量值推送至栈顶
     5: if_icmpge     14    // 比较栈顶两int型数值大小,当结果大于等于0时跳转到14行
     8: iinc          1, 1  // 指定int型变量增加指定值,即自增1
    11: goto          2     // 无条件跳转
    14: return              // 当前方法返回void

注意:比较while 和 for 的字节码,会发现它们是一模一样的,殊途也能同归。所以我们编写的while 循环 与 for循环在底层是一样的执行。

构造方法

public class demo{ 
        
    static int i = 10;
    
    static{ 
        
        i = 20;
    }
    
    static{ 
        
        i = 30;
    }
}

编译器会按从上至下的顺序,收集所有static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法()V:

stack=1, locals=0, args_size=0
         0: bipush        10
         2: putstatic     #3 // Field i:I
         5: bipush        20
         7: putstatic     #3 // Field i:I
        10: bipush        30
        12: putstatic     #3 // Field i:I
        15: return

public class Demo4 { 
        
	private String a = "s1";

	{ 
        
		b = 20;
	}

	private int b = 10;

	{ 
        
		a = "s2";
	}

	public Demo4(String a, int b) { 
        
		this.a = a;
		this.b = b;
	}

	public static void main(String[] args) { 
        
		Demo4 d = new Demo4("s3", 30);
		System.out.println(d.a);
		System.out.println(d.b);
	}
}

编译器会按的顺序,收集所有 {} 代码块和成员变量赋值的代码,,但内的代码

Code:
     stack=2, locals=3, args_size=3
        0: aload_0
        1: invokespecial #1 // Method java/lang/Object."<init>":()V
        4: aload_0
        5: ldc           #2 // String s1
        7: putfield      #3 // Field a:Ljava/lang/String;
       10: aload_0
       11: bipush        20
       13: putfield      #4 // Field b:I
       16: aload_0
       17: bipush        10
       19: putfield      #4 // Field b:I
       22: aload_0
       23: ldc           #5 // String s2
       25: putfield      #3 // Field a:Ljava/lang/String;
       //原始构造方法在最后执行
       28: aload_0
       29: aload_1
       30: putfield      #3 // Field a:Ljava/lang/String;
       33: aload_0
       34: iload_2
       35: putfield      #4 // Field b:I
       38: return

方法调用

public class Demo5 { 
        
	public Demo5() { 
        

	}

	private void test1() { 
        

	}

	private final void test2() { 
        

	}

	public void test3() { 
        

	}

	public static void test4() { 
        

	}

	public static void main(String[] args) { 
        
		Demo5 demo5 = new Demo5();
		demo5.test1();
		demo5.test2();
		demo5.test3();
		Demo5.test4();
	}
}

不同方法在调用时,对应的虚拟机指令有所区别

  • 私有、构造、被final修饰的方法,在调用时都使用指令
  • 普通成员方法在调用时,使用指令。因为编译期间无法确定该方法的内容,只有在运行期间才能确定
  • 静态方法在调用时使用指令

字节码

Code:
      stack=2, locals=2, args_size=1
         0: new           #2 // class com/nyima/JVM/day5/Demo5 
         3: dup
         4: invokespecial #3 // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokespecial #4 // Method test1:()V
        12: aload_1
        13: invokespecial #5 // Method test2:()V
        16: aload_1
        17: invokevirtual #6 // Method test3:()V
        20: invokestatic  #7 // Method test4:()V
        23: return
  • new 是创建【对象】,给对象分配堆内存,执行成功会将【】压入操作数栈
  • dup 是赋值操作数栈栈顶的内容,本例即为【】,为什么需要两份引用呢,一个是要配合 invokespecial 调用该对象的构造方法 “init”😦)V (会消耗掉栈顶一个引用),另一个要 配合 astore_1 赋值给局部变量

多态原理

public class Demo2 { 
        
    public static void test(Animal animal){ 
        
        animal.eat();
        System.out.println(animal.toString());
    }

    public static void main(String[] args) throws IOException { 
        
        test(new Cat());
        test(new Dog());
        System.in.read();
    }


    static abstract class Animal{ 
        
        public abstract void eat();
        @Override
        public String toString() { 
        
            return "我是" + this.getClass().getSimpleName();
        }
    }


    static class Dog extends Animal{ 
        
        @Override
        public void eat() { 
        
            System.out.println("啃骨头");
        }
    }

     static class Cat extends Animal{ 
        
        @Override
        public void eat() { 
        
            System.out.println("吃鱼");
        }
    }
}

在jdk目录下输入下列命令

java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB

使用jps指令查看进程数

根据进程号在HSDB中连接

因为普通成员方法需要在运行时才能确定具体的内容,所以虚拟机需要调用指令

在执行invokevirtual指令时,经历了以下几个步骤

  • 先通过栈帧中对象的引用找到对象
  • 分析对象头,找到对象实际的Class
  • Class结构中有
  • 查询vtable找到方法的具体地址
  • 执行方法的字节码

异常捕获

public class Demo1 { 
        
	public static void main(String[] args) { 
        
		int i = 0;
		try { 
        
			i = 10;
		}catch (Exception e) { 
        
			i = 20;
		}
	}
}

对应字节码指令

Code:
     stack=1, locals=3, args_size=1
        0: iconst_0
        1: istore_1
        2: bipush        10
        4: istore_1
        5: goto          12
        8: astore_2
        9: bipush        20
       11: istore_1
       12: return
     //多出来一个异常表
     Exception table:
        from    to  target type
            2     5     8   Class java/lang/Exception
  • 可以看到多出来一个 Exception table 的结构,[from, to) 是(也就是检测2~4行)的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
  • 8行的字节码指令 astore_2 是将异常对象引用存入局部变量表的2号位置(为e)

public class Demo1 { 
        
	public static void main(String[] args) { 
        
		int i = 0;
		try { 
        
			i = 10;
		}catch (ArithmeticException e) { 
        
			i = 20;
		}catch (Exception e) { 
        
			i = 30;
		}
	}
}

对应的字节码

Code:
     stack=1, locals=3, args_size=1
        0: iconst_0
        1: istore_1
        2: bipush        10
        4: istore_1
        5: goto          19
        8: astore_2
        9: bipush        20
       11: istore_1
       12: goto          19
       15: astore_2
       16: bipush        30
       18: istore_1
       19: return
     Exception table:
        from    to  target type
            2     5     8   Class java/lang/ArithmeticException
            2     5    15   Class java/lang/Exception
  • 因为异常出现时, Exception table 中,所以局部变量表 slot 2 位置

public class Demo2 { 
        
	public static void main(String[] args) { 
     

标签: 智能温度变送器ad180fd3fd3y96变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台