JVM_09 类加载与字节码技术(字节码指令3)

Posted 兴趣使然の草帽路飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM_09 类加载与字节码技术(字节码指令3)相关的知识,希望对你有一定的参考价值。

1.异常处理

try-catch

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

注意:为了抓住重点,下面的字节码省略了不重要的部分

字节码如下:

public static void main(java.lang.String[]);
	descriptor: ([Ljava/lang/String;)V
	flags: ACC_PUBLIC, ACC_STATIC
	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
		LineNumberTable: ...
		LocalVariableTable:
			Start Length Slot Name Signature
				9      3    2    e Ljava/lang/Exception;
		        0 	  13    0 args [Ljava/lang/String;
				2 	  11 	1    i         I
		StackMapTable: ...
		MethodParameters: ...
}
  • 可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
  • 8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置

多个 single-catch 块的情况

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

字节码如下:

public static void main(java.lang.String[]);
	descriptor: ([Ljava/lang/String;)V
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=1, locals=3, args_size=1
			0: iconst_0
			1: istore_1
			2: bipush 		10
			4: istore_1
			5: goto 		26
			8: astore_2
			9: bipush 		30
			11: istore_1
			12: goto 		26
			15: astore_2
			16: bipush 		40
			18: istore_1
			19: goto 		26
			22: astore_2
			23: bipush 		50
			25: istore_1
			26: return
		Exception table:
			from to target type
			   2  5     8  Class java/lang/ArithmeticException
			   2  5    15  Class java/lang/NullPointerException
			   2  5    22  Class java/lang/Exception
		LineNumberTable: ...
		LocalVariableTable:
			Start Length Slot Name Signature
			   9       3    2    e Ljava/lang/ArithmeticException;
			  16       3    2    e Ljava/lang/NullPointerException;
			  23       3    2    e Ljava/lang/Exception;
			   0      27    0 args Ljava/lang/String;
			2 25 1 i I
		StackMapTable: ...
		MethodParameters: ...
  • 因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用。

multi-catch 的情况

public class Demo3_11_3 {
	public static void main(String[] args) {
		try {
			Method test = Demo3_11_3.class.getMethod("test");
			test.invoke(null);
		} catch (NoSuchMethodException | IllegalAccessException |
			InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	public static void test() {
	System.out.println("ok");
	}
}

字节码如下:

public static void main(java.lang.String[]);
	descriptor: ([Ljava/lang/String;)V
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=3, locals=2, args_size=1
			0: ldc 				#2
			2: ldc 				#3
			4: iconst_0
			5: anewarray 		#4
			8: invokevirtual 	#5
			11: astore_1
			12: aload_1
			13: aconst_null
			14: iconst_0
			15: anewarray		 #6
			18: invokevirtual 	 #7
			21: pop
			22: goto			 30
			25: astore_1
			26: aload_1
			27: invokevirtual	 #11 // e.printStackTrace:()V
			30: return
		Exception table:
			from to target type
			   0 22     25 Class java/lang/NoSuchMethodException
			   0 22     25 Class java/lang/IllegalAccessException
			   0 22     25 Class java/lang/reflect/InvocationTargetException
		LineNumberTable: ...
		LocalVariableTable:
			Start Length Slot Name Signature
			  12      10    1 test Ljava/lang/reflect/Method;
			  26       4    1    e Ljava/lang/ReflectiveOperationException;
			   0      31    0 args [Ljava/lang/String;
		StackMapTable: ...
		MethodParameters: ...

finally

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

字节码如下:

public static void main(java.lang.String[]);
	descriptor: ([Ljava/lang/String;)V
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=1, locals=4, args_size=1
			0: iconst_0
			1: istore_1 		// 0 -> i
			2: bipush     10 	// try --------------------------------------
			4: istore_1 		// 10 -> i 							|
			5: bipush     30 	// finally 							|
			7: istore_1 		// 30 -> i 							|
			8: goto       27 	// return -----------------------------------
			11: astore_2 		// catch Exceptin -> e ----------------------
			12: bipush 	  20 	// 									|
			14: istore_1 		// 20 -> i 							|
			15: bipush    30 	// finally 							|
			17: istore_1 		// 30 -> i 							|
			18: goto 	  27 	// return -----------------------------------
			21: astore_3 		// catch any -> slot 3 ----------------------
			22: bipush 	  30 	// finally 							|
			24: istore_1 		// 30 -> i 							|
			25: aload_3 		// <- slot 3 						|
			26: athrow 			// throw ------------------------------------
			27: return
		Exception table:
				from to target type
				   2  5     11 Class java/lang/Exception
				   2  5     21  any // 剩余的异常类型,比如 Error
				  11 15     21  any // 剩余的异常类型,比如 Error
		LineNumberTable: ...
		LocalVariableTable:
				Start Length Slot Name Signature
				  12       3    2    e Ljava/lang/Exception;
				   0      28    0 args [Ljava/lang/String;
				   2      26    1    i I
		StackMapTable: ...
		MethodParameters: ...

可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程。


2.练习:finally面试题

finally 出现了 return

下面的题目输出什么?

public class Demo3_12_2 {
	public static void main(String[] args) {
		int result = test();
		System.out.println(result);// 20
	}
	public static int test() {
		try {
			return 10;
		} finally {
			return 20;
		}
	}
}

字节码如下:

public static int test();
	descriptor: ()I
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=1, locals=2, args_size=0
			0: bipush 		10 	// <- 10 放入栈顶
			2: istore_0 		// 10 -> slot 0 (从栈顶移除了)
			3: bipush 		20 	// <- 20 放入栈顶
			5: ireturn 			// 返回栈顶 int(20)
			6: astore_1 		// catch any -> slot 1
			7: bipush 		20 	// <- 20 放入栈顶
			9: ireturn 			// 返回栈顶 int(20)
		Exception table:
			from to target type
			   0  3     6   any
		LineNumberTable: ...
		StackMapTable: .
  • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
  • 至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子。
  • 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常,可以试一下下面。
.
public class Demo3_12_1 {
	public static void main(String[] args) {
		int result = test();
		System.out.println(result);
	}
	public static int test() {
		try {
			int i = 1/0;
			return 10;
		} finally {
			return 20;
		}
	}
}

finally 对返回值的影响

自己,下面的题目输出什么?

}
public class Demo3_12_2 {
	public static void main(String[] args) {
		int result = test();
		System.out.println(result);// 20
	}
	public static int test() {
		int i = 10;
		try {
			return i;
		} finally {
			i = 20;
		}
	}
}

字节码如下:

public static int test();
	descriptor: ()I
	flags: ACC_PUBLIC, ACC_STATIC
	Code:
		stack=1, locals=3, args_size=0
			0: bipush 		10 		// <- 10 放入栈顶
			2: istore_0 			// 10 -> i
			3: iload_0				// <- i(10)
			4: istore_1 			// 10 -> slot 1,暂存至 slot 1,目的是为了固定返回值
			5: bipush		20 		// <- 20 放入栈顶
			7: istore_0 			// 20 -> i
			8: iload_1			    // <- slot 1(10) 载入 slot 1 暂存的值
			9: ireturn 				// 返回栈顶的 int(10)
			10: astore_2
			11: bipush 		20
			13: istore_0
			14: aload_2
			15: athrow
		Exception table:
			from to target type
			     3 5   10   any
		LineNumberTable: ...
		LocalVariableTable:
			Start Length Slot Name Signature
			   3      13    0    i         I
		StackMapTable: ...

3.synchronized关键字

public class Demo3_13 {
	public static void main(String[] args) {
		Object lock = new Object();
		synchronized (lock) {
			System.out.println("ok");
		}
	}
}

字节码如下:

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: new 				#2 	// new Object
			3: dup
			4: invokespecial 	#1 	// invokespecial <init>:()V
			7: astore_1 			// lock引用 -> lock
			8: aload_1 				// <- lock (synchronized开始)
			9: dup
			10: astore_2 			// lock引用 -> slot 2
			11: monitorenter 		// monitorenter(lock引用)
			12: getstatic 		#3  // <- System.out
			15: ldc 				// <- "ok
			17: invokevirtual 	#5 	// invokevirtual println:
			(Ljava/lang/String;)V
			20: aload_2 			// <- slot 2(lock引用)
			21: monitorexit 		// monitorexit(lock引用)
			22: goto 			30
			25: astore_3 			// any -> slot 3
			26: aload_2 			// <- slot 2(lock引用)
			27: monitorexit 		// monitorexit(lock引用)
			28: aload_3
			29: athrow
			30: return
		Exception table:
			from to target type
			  12 22    25   any
			  25 28    25  any
		LineNumberTable: ...
		LocalVariableTable:
			Start Length Slot Name Signature
			    0     31   0  args [Ljava/lang/String;
			    8     23   1  lock Ljava/lang/Object;
		StackMapTable: ...
		MethodParameters: ...

注意:方法级别的 synchronized 不会在字节码指令中有所体现

以上是关于JVM_09 类加载与字节码技术(字节码指令3)的主要内容,如果未能解决你的问题,请参考以下文章

黑马程序员JVM教程笔记完整目录

黑马程序员JVM教程笔记完整目录

JVM_07 类加载与字节码技术(字节码指令)

JVM_07 类加载与字节码技术(字节码指令)

(超详解)JVM-类加载与字节码技术

JVM_08 类加载与字节码技术(字节码指令2)