JVM系列笔记--字节码与类加载

9 篇文章 0 订阅

一、字节码指令

反编译

  • 可使用javap -v className.class来反编译class文件
    public class Demo3_1 {
        public static void main(String[] args) {
            int a = 10;
            int b = Short.MAX_VALUE + 1;
            int c = a + b;
            System.out.println(c);
        }
    }
    
    Classfile /D:/downloads/资料 解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t3/bytecode/Demo3_1.class
      Last modified 2020-11-2; size 635 bytes
      MD5 checksum 1a6413a652bcc5023f130b392deb76a1
      Compiled from "Demo3_1.java"
    public class cn.itcast.jvm.t3.bytecode.Demo3_1
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#25         // java/lang/Object."<init>":()V
       #2 = Class              #26            // java/lang/Short
       #3 = Integer            32768
       #4 = Fieldref           #27.#28        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = Methodref          #29.#30        // java/io/PrintStream.println:(I)V
       #6 = Class              #31            // cn/itcast/jvm/t3/bytecode/Demo3_1
       #7 = Class              #32            // java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               LocalVariableTable
      #13 = Utf8               this
      #14 = Utf8               Lcn/itcast/jvm/t3/bytecode/Demo3_1;
      #15 = Utf8               main
      #16 = Utf8               ([Ljava/lang/String;)V
      #17 = Utf8               args
      #18 = Utf8               [Ljava/lang/String;
      #19 = Utf8               a
      #20 = Utf8               I
      #21 = Utf8               b
      #22 = Utf8               c
      #23 = Utf8               SourceFile
      #24 = Utf8               Demo3_1.java
      #25 = NameAndType        #8:#9          // "<init>":()V
      #26 = Utf8               java/lang/Short
      #27 = Class              #33            // java/lang/System
      #28 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
      #29 = Class              #36            // java/io/PrintStream
      #30 = NameAndType        #37:#38        // println:(I)V
      #31 = Utf8               cn/itcast/jvm/t3/bytecode/Demo3_1
      #32 = Utf8               java/lang/Object
      #33 = Utf8               java/lang/System
      #34 = Utf8               out
      #35 = Utf8               Ljava/io/PrintStream;
      #36 = Utf8               java/io/PrintStream
      #37 = Utf8               println
      #38 = Utf8               (I)V
    {
      public cn.itcast.jvm.t3.bytecode.Demo3_1();
        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   Lcn/itcast/jvm/t3/bytecode/Demo3_1;
    
      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: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            13: iload_3
            14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
            17: return
          LineNumberTable:
            line 8: 0
            line 9: 3
            line 10: 6
            line 11: 10
            line 12: 17
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      18     0  args   [Ljava/lang/String;
                3      15     1     a   I
                6      12     2     b   I
               10       8     3     c   I
    }
    SourceFile: "Demo3_1.java"
    

执行流程

  • main方法中指明了局部变量表数目、操作数栈的深度、方法参数的数目stack=2, locals=4, args_size=1
    在这里插入图片描述
    1. bipush 10:将一个 byte 压入操作数栈
    2. istore_1:将操作数栈顶数据弹出,存入局部变量表的slot 1
    3. ldc #3:从常量池加载#3数据到操作数栈
    4. istore_2:将操作数栈顶数据弹出,存入局部变量表的slot 2
    5. iload_1:从局部变量表slot 1中取出数据,存入操作数栈中
    6. iload_2
    7. iadd:将操作数栈中的数据相加
    8. istore_3:从操作数栈顶将数据弹出,存入局部变量表的slot 3
    9. getstatic #4
      在这里插入图片描述
    10. iload_3
    11. invokevirtual #5:找到常量池 #5 项,定位到方法区java/io/PrintStream.println:(I)V方法,生成新的栈帧(分配locals、stack等),传递参数,执行新栈帧中的字节码
      在这里插入图片描述
    12. 执行完毕,弹出栈帧
    13. 清除main操作数栈的内容

加载和存储指令

  • 将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
  • 将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
  • 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
  • 扩充局部变量表的访问索引的指令:wide

运算指令

  • 加法指令:iadd、ladd、fadd、dadd
  • 减法指令:isub、lsub、fsub、dsub
  • 乘法指令:imul、lmul、fmul、dmul
  • 除法指令:idiv、ldiv、fdiv、ddiv
  • 求余指令:irem、lrem、frem、drem
  • 取反指令:ineg、lneg、fneg、dneg
  • 位移指令:ishl、ishr、iushr、lshl、lshr、lushr
  • 按位或指令:ior、lor
  • 按位与指令:iand、land
  • 按位异或指令:ixor、lxor
  • 局部变量自增指令:iinc
  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

对象创建与访问指令

  • 创建类实例的指令:new
  • 创建数组的指令:newarray、anewarray、multianewarray
  • 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic
  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
  • 将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
  • 取数组长度的指令:arraylength
  • 检查类实例类型的指令:instanceof、checkcast

操作数栈管理指令

  • 将操作数栈的栈顶一个或两个元素出栈:pop、pop2
  • 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
  • 将栈最顶端的两个数值互换:swap

控制转移指令

  • 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne
  • 复合条件分支:tableswitch、lookupswitch
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret

方法调用和返回指令

  • invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
  • invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
  • invokestatic指令:用于调用类静态方法(static方法)。
  • invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在Java虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

二、语法糖

默认构造器

public class Candy1 {
}

编译成class文件后的代码:

public class Candy1 {
// 这个无参构造是编译器帮助我们加上的
	public Candy1() {
		super(); // 即调用父类 Object 的无参构造方法,即调用 java/lang/Object."<init>":()V
	}
}

自动装拆箱

public class Candy2 {
	public static void main(String[] args) {
		Integer x = 1;
		int y = x;
	}
}

该特性事在JDK 5 后加入的,工作原理如下:

public class Candy2 {
	public static void main(String[] args) {
		Integer x = Integer.valueOf(1);
		int y = x.intValue();
	}
}

泛型擦除

Java在编译后会执行泛型擦除动作,实际类型都被当成Object类型处理,因此在取值时,就需要额外进行类型转换操作

public class Candy3 {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<>();
		list.add(10); // 实际调用的是 List.add(Object e)
		Integer x = list.get(0); // 实际调用的是 Object obj = List.get(int index);
	}
}
// 类型转换 需要将 Object 转为 Integer
Integer x = (Integer)list.get(0);

//如果x是int类型,则需要进行拆箱操作
int x = ((Integer)list.get(0)).invalue();

可变参数

public class Candy4 {
	public static void foo(String... args) {
		String[] array = args; // 直接赋值
		System.out.println(array);
	}
	public static void main(String[] args) {
		foo("hello", "world");
	}
}

在编译期间,可变参数会转换成一个数组

public class Candy4 {
public static void foo(String[] args) {
	String[] array = args; // 直接赋值
		System.out.println(array);
	}
	public static void main(String[] args) {
		foo(new String[]{"hello", "world"});
	}
}

foreach循环

  • 普通foreach循环
    public class Candy5_1 {
    	public static void main(String[] args) {
    		int[] array = {1, 2, 3, 4, 5}; // 数组赋初值的简化写法也是语法糖哦
    		for (int e : array) {
    			System.out.println(e);
    		}
    	}
    }
    
    public class Candy5_1 {
    	public Candy5_1() {
    	}
    	public static void main(String[] args) {
    		int[] array = new int[]{1, 2, 3, 4, 5};
    		for(int i = 0; i < array.length; ++i) {
    			int e = array[i];
    			System.out.println(e);
    		}
    	}
    }
    
  • 集合
    public class Candy5_2 {
    	public static void main(String[] args) {
    		List<Integer> list = Arrays.asList(1,2,3,4,5);
    		for (Integer i : list) {
    			System.out.println(i);
    		}
    	}
    }
    
    public class Candy5_2 {
    	public Candy5_2() {
    	}
    	public static void main(String[] args) {
    		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    		Iterator iter = list.iterator();
    		while(iter.hasNext()) {
    			Integer e = (Integer)iter.next();
    			System.out.println(e);
    		}
    	}
    }
    

switch字符串

public class Candy6_1 {
	public static void choose(String str) {
		switch (str) {
			case "hello": {
				System.out.println("h");
				break;
			}
			case "world": {
				System.out.println("w");
				break;
			}
		}
	}
}

编译时,会进行两重校验,对需要case的内容转换成hashcode,进行比较;而进行equals时,是为了防止hashcode冲突

public class Candy6_1 {
	public Candy6_1() {
	}
	public static void choose(String str) {
		byte x = -1;
		switch(str.hashCode()) {
		case 99162322: // hello 的 hashCode
			if (str.equals("hello")) {
				x = 0;
			}
			break;
		case 113318802: // world 的 hashCode
			if (str.equals("world")) {
				x = 1;
			}
		}
		switch(x) {
		case 0:
			System.out.println("h");
			break;
		case 1:
			System.out.println("w");
		}
	}
}

匿名内部类

public class Candy11 {
	public static void test(final int x) {
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("ok:" + x);
			}
		};
	}
}

编译后会额外生成一个类

// 额外生成的类
final class Candy11$1 implements Runnable {
	int val$x;
	Candy11$1(int x) {
		this.val$x = x;
	}
	public void run() {
		System.out.println("ok:" + this.val$x);
	}
}
public class Candy11 {
	public static void test(final int x){
		Runnable runnable = new Candy11$1(x);
	}
}

三、类加载

在这里插入图片描述

  • 加载:
    1. 通过一个类的全限定名来获取定义此类的二进制字节流
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
    3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 验证:确保Class文件的字节流中包含的信息符合规范
    1. 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
    2. 元数据验证:对字节码描述的信息进行语义分析
    3. 字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的
    4. 符号引用验证:对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验
  • 准备:正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值
    1. 这些变量所使用的内存都应当在方法区中进行分配
    2. 进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中
  • 解析:Java虚拟机将常量池内的符号引用替换为直接引用的过程
    1. 类或者接口的解析
    2. 字段解析
    3. 方法解析
    4. 接口方法解析
  • 初始化:初始化阶段就是执行类构造器<clinit>()方法的过程
    1. <clinit>()方法是由编译器自动收集类中所有的类变量的赋值动作和静态语句块中的语句合并产生
    2. <clinit>()不需要显式地调用父类构造器,Java虚拟机会确保父类<clinit>()在子类<clinit>()之前执行
    class A {
    	static int a = 0;
    	static {
    		System.out.println("a init");
    	}
    }
    class B extends A {
    	final static double b = 5.0;
    	static boolean c = false;
    	static {
    		System.out.println("b init");
    	}
    }
    
    public class Load3 {
    	static {
    		System.out.println("main init");
    	}
    	public static void main(String[] args) throws ClassNotFoundException {
    		// 1. 静态常量(基本类型和字符串)不会触发初始化
    		System.out.println(B.b);
    		// 2. 类对象.class 不会触发初始化
    		System.out.println(B.class);
    		// 3. 创建该类的数组不会触发初始化
    		System.out.println(new B[0]);
    		// 4. 不会初始化类 B,但会加载 B、A
    		ClassLoader cl = Thread.currentThread().getContextClassLoader();
    		cl.loadClass("cn.itcast.jvm.t3.B");
    		// 5. 不会初始化类 B,但会加载 B、A
    		ClassLoader c2 = Thread.currentThread().getContextClassLoader();
    		Class.forName("cn.itcast.jvm.t3.B", false, c2);
    		// 1. 首次访问这个类的静态变量或静态方法时
    		System.out.println(A.a);
    		// 2. 子类初始化,如果父类还没初始化,会引发
    		System.out.println(B.c);
    		// 3. 子类访问父类静态变量,只触发父类初始化
    		System.out.println(B.a);
    		// 4. 会初始化类 B,并先初始化类 A
    		Class.forName("cn.itcast.jvm.t3.B");
    	}
    }
    

四、类加载器

双亲委派模型

在这里插入图片描述

  • 如果一个类加载器收到了类加载的请求,它首先将请求委派给父类加载器去完成,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值