异常表

代码一:

public class Test03 {
public void test() {
try {
InputStream is = new FileInputStream("123.txt"); ServerSocket serverSocket = new ServerSocket(1234);
serverSocket.accept();
} catch (FileNotFoundException f) { } catch (IOException i) { } catch (Exception e) { } finally {
System.out.println("finally");
}
}
}

编译代码一,然后利用javap进行反编译查看结果:

...
Code:
stack=3, locals=4, args_size=1
0: new #2 // class java/io/FileInputStream
3: dup
4: ldc #3 // String 123.txt
6: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #5 // class java/net/ServerSocket
13: dup
14: sipush 1234
17: invokespecial #6 // Method java/net/ServerSocket."<init>":(I)V
20: astore_2
21: aload_2
22: invokevirtual #7 // Method java/net/ServerSocket.accept:()Ljava/net/Socket;
25: pop
26: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #9 // String finally
31: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: goto 84
37: astore_1
38: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
41: ldc #9 // String finally
43: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: goto 84
49: astore_1
50: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #9 // String finally
55: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: goto 84
61: astore_1
62: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
65: ldc #9 // String finally
67: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
70: goto 84
73: astore_3
74: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
77: ldc #9 // String finally
79: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
82: aload_3
83: athrow
84: return
Exception table:
from to target type
0 26 37 Class java/io/FileNotFoundException
0 26 49 Class java/io/IOException
0 26 61 Class java/lang/Exception
0 26 73 any
...

因为我们讨论是异常表,所以我只贴处test方法的code attribute的部分内容。

首先看第一行:stack=3, locals=4, args_size=1stack=3表示test方法运行的任何时刻所能达到的操作数栈的最大深度为3,locals=4表示test方法执行期间创建的局部变量的数目为4,args_size=1表示test方法的参数个数为1。这时我们过来看代码,局部变量表的个数:catch块中,首先InputStream,然后ServerSocket,共有2个局部变量;catch块中会产生一个局部变量;finally中没有局部变量;那么也就只有3个局部变量,而不是4个。而且,test方法没有参数,为什么 args_size的值为1。

这里需要引入一个知识点:this关键字

对于Java中的每一个实例方法(非static方法),其在编译后所生成的字节码中, 方法参数的数量总是比源代码中方法参数的数量多一个(this),它位于方法的第一个参数。这样,我们就可以在Java的实例方法中使用this来去访问当前对象的属性以及其他方法。这个操作是在编译期间完成的,即由javac编译器在编译的时候将对this的访问转换为对一个普通实例方法参数的访问。接下来在运行期间,由JVM调用实例方法时,自动向实例方法传入该this参数。所以,在实例方法的局部变量中,至少会有一个指向当前对象的局部变量。

接下来看异常表:

  Exception table:
from to target type
0 26 37 Class java/io/FileNotFoundException
0 26 49 Class java/io/IOException
0 26 61 Class java/lang/Exception
0 26 73 any

异常表中有4个值,每个值的start_pc都为0,end_pc都为26,这是因为catch块catch的都是try块中的内容;handler_pc(处理异常的代码的开始处)分别为37、49、61、73,我们可以发现每段异常的方法都是相同的(因为catch本身就没有写处理方法,直接进入finally块),我们可以发现;catch_type分别是FileNotFoundException、IOException、Exception、any,前三个是catch块catch的异常,any表示处理任何异常。

Java字节码对于异常的处理方式:

  • 统一采用异常表的方式来对异常进行处理。
  • 在jdk1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理,而是采用特定的指令。
  • 当异常处理存在finally语句块时,现代化的JVM采用的处理方式是将finally语句块的字节码拼接到每一个catch块的后面。换句话说,程序中存在多少个catch块,就会有在每个catch块后面重复多少个finally语句块的字节码。

栈帧与操作数栈

  • 栈帧(stack frame):栈帧是一种用于帮助虚拟机执行方法调用及方法执行的数据结构。
  • 栈帧本身是一种数据结构,封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。

符号引用与直接引用

比如现有两个类:A类和B类,A类存在对B类的方法调用,在编译期间,A对B的方法调用,它们之间的地址关系是不知道的。

什么时候才能知道呢?

  • 类加载的时候
  • 真正开始调用的时候

符号引用:A类去调用B类,在A的常量池中,会维护B的全局唯一限定名

直接引用:直接在内存中寻找到被调用的方法

  • 有些符号引用是在类加载阶段或是第一次使用时就会转换为直接引用,这种转换叫做静态解析

    • 静态解析的四种情形,以下四类方法称作非虚方法,他们是在类加载阶段就可以将符号引用转换为直接引用

      • 静态方法

      • 父类方法

      • 构造方法

      • 私有方法

  • 另外一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接,这体现为Java的多态性

方法的静态分派和动态分派

方法的静态分派

Grandpa g1 = new Father();g1的静态类型是Grandpa,该g1的实际类型(真正指向的类型)是Father

结论:变量的静态类型是不会发生变化,而变量的实际类型则是可以发生变化的(多态的一种体现),实际类型在运行期方可确定。

方法的动态分派:

nvokevirtual字节码指令的动态查找流程

  • 寻找操作数栈的栈顶的元素所指向的对象的实际类型
  • 如果在实际类型在当中寻找到了与常量池中描叙符和名称都相同的方法,并且具备相应的访问权限,返回目标方法的直接引用
  • 如果找不到,就按照继承的层次关系从子类往父类依次重复查找流程;如果一直找不到,则抛出异常

通过比较方法重载和方法重写,得到这样一个结论:

  • 方法重载是静态的,是编译期行为
  • 方法重写是动态的,是运行期行为

基于栈的指令集于基于寄存器的指令集

现代JVM在执行Java代码的时候,通常会将解释执行和编译执行二者结合起来执行。

  • 解释执行:通过解释器来读取字节码,遇到相应的指令就去执行该指令。
  • 编译执行:通过即时编译器(JIT:Just In Time)将字节码转换为本地机器码来执行;现代JVM会根据代码热点来生成相应的本地机器码

基于栈的指令集与基于寄存器的指令集之间的关系:

  • JVM执行指令时所采取的方式基于栈的指令集
  • 基于栈的指令集主要的操作有入栈和出栈两种
  • 基于栈的指令集的优势在于它可以在不同平台之间移植;而基于寄存器的指令集是与硬件架构紧密相关的,无法做到可移植
  • 基于栈的指令集缺点在于完全相同的操作,指令数量通常要比基于寄存器的指令集数量要多 基于栈的指令集是在内存中完成操作的,而基于寄存器的指令集是直接由CPU来执行的,它是在高速缓冲区中进行执行的,速度要快很多 虽然虚拟机可以采用一些优化手段,但总体来说,基于栈的指令集的执行速度要慢一些

Java字节码扩展的更多相关文章

  1. Java字节码操纵框架ASM小试

    本文主要内容: ASM是什么 JVM指令 Java字节码文件 ASM编程模型 ASM示例 参考资料汇总 JVM详细指令 ASM是什么 ASM是一个Java字节码操纵框架,它能被用来动态生成类或者增强既 ...

  2. Java字节码 小结

    Reference javap 基本使用方法 深入理解java字节码 从Java代码到字节码 Java字节码.class文件案例分析 字节码 核心概念 Class文件是8位字节流,按字节对齐.之所以称 ...

  3. JVM 内部原理(六)— Java 字节码基础之一

    JVM 内部原理(六)- Java 字节码基础之一 介绍 版本:Java SE 7 为什么需要了解 Java 字节码? 无论你是一名 Java 开发者.架构师.CxO 还是智能手机的普通用户,Java ...

  4. 轻松看懂Java字节码

    java字节码 计算机只认识0和1.这意味着任何语言编写的程序最终都需要经过编译器编译成机器码才能被计算机执行.所以,我们所编写的程序在不同的平台上运行前都要经过重新编译才能被执行. 而Java刚诞生 ...

  5. @使用javap反编译Java字节码文件

    在Sun公司提供的JDK中,就已经内置了Java字节码文件反编译工具javap.exe(位于JDK安装目录的bin文件夹下). 我们可以在dos窗口中使用javap来反汇编指定的Java字节码文件.在 ...

  6. Java字节码

    Java字节码 javap -c 反编译.class文件可得字节码 知乎讨论https://www.zhihu.com/question/27831730 栈和局部变量操作 将常量压入栈的指令 aco ...

  7. 从Java源码到Java字节码

    Java最主流的源码编译器,javac,基本上不对代码做优化,只会做少量由Java语言规范要求或推荐的优化:也不做任何混淆,包括名字混淆或控制流混淆这些都不做.这使得javac生成的代码能很好的维持与 ...

  8. Java字节码指令收集大全

    Java字节码指令大全 常量入栈指令 指令码 操作码(助记符) 操作数 描述(栈指操作数栈) 0x01 aconst_null null值入栈. 0x02 iconst_m1 -1(int)值入栈. ...

  9. Java字节码深度剖析

    Java字节码文件查看 我们有一个类Test01,具体内容如下: package bytecode; public class Test01 { private int i = 0; public i ...

随机推荐

  1. windows cmd编辑文本

    echo创建一个空的txt文件:echo.>1.txt这里>表示输出到...echo.表示输出一个空行(即换行)>命令可以扩展为>>表示的意思为附加到...例子:1.tx ...

  2. [linux][c++]linux c++ 通过xcb库获取屏幕大小

    linux c++ 通过xcb库获取屏幕大小 #include <stdio.h> #include <xcb/xcb.h> /** clang++ main.cpp -o m ...

  3. 第08组 Alpha冲刺(4/6)

    队名:955 组长博客:https://www.cnblogs.com/cclong/p/11882079.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...

  4. H5中对history栈的操作

    今天研究一下H5中history操作的相关知识,首先梳理一下基本内容: 一.在history中的跳转 使用 back(), forward()和 go() 方法来完成在用户历史记录中向后和向前的跳转. ...

  5. [技术博客]基于动态继承类、WebDriver的浏览器兼容性测试框架搭建

    问题背景 观察使用selenium进行自动化测试的过程,我们可以将它概述为: 启动测试进程,在该进程中构建WebDriver 启动浏览器进程,将它与WebDriver建立连接 使用WebDriver向 ...

  6. ethtool 强制设置网卡运行模式为100M

    ethtool -s eth0 autoneg off speed 100 duplex full

  7. Declaration of Admin\Controller\GameController::delete() should be compatible with。。

      NOTIC: [2048] Declaration of Admin\Controller\GameController::delete() should be compatible with A ...

  8. Xamarin.FormsShell基础教程(2)创建Shell解决方案

    Xamarin.FormsShell基础教程(2)创建Shell解决方案 创建Shell解决方案 在开发Shell的应用程序时,首先需要创建一个Shell解决方案,其具体操作步骤如下: (1)在VS的 ...

  9. 统计git提交代码量

    # a新增行数,d删除行数 git log  --author="`git config --get user.name`" --pretty="%H" --a ...

  10. ES技巧

    2, 统计字段b的不同值的数量 {"size":0,"aggs":{"distinct_colors":{"cardinality ...