目录
- 1. 简单源码
- 2. 奇怪的地方
- 3. 结语
大家都知道,Java写的代码最后执行的时候,会被编译成字节码,那字节码到底是啥?今天我们就以人类的方式来看一下字节码里面到底存了啥,class文件结构里面的各个字段又都表达了哪些信息。
1. 简单源码
为了说明整体结构,我们先用一个最简单的源文件来编译,源码如下:
package klass.demo;
public class DemoHello {
/**
* 这里是要说的话
*/
private String words;
public String say() {
words = "hello";
return words;
}
}
运行命令:
javac klass/demo/DemoHello.java
然后请出今天的主角javap命令,它的可选参数如下
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
为了更全面的展示字节码信息,我们使用-v参数,命令如下:
javap -v klass.demo.DemoHello
这样我们便可以得到的结果如下庞大的信息:
这下是不是比之前的01文件清晰多了,文件中大致分成了5个部分,我们按照行号范围依次跟大家分析一下
A,1-4
这部分是文件的基本属性,包括名字、最后修改时间、大小、MD5等信息比较简单
B、5-8
这部分是关于类的基本属性,包括名字、可使用的JVM版本区间、还有一个访问控制符,这里是public
C、9-29
这部分占据了比较大的空间,顾名思义就是运行时常量池了,类似于符号表,每个元素都有一个id,比如#2、#8,紧接着是数据类型,比如MethodRef表示方法、class表示类、Utf8表示一些字面量
D、30-56
这也是一个占据空间的主力军,主要是描述了类的成员方法。每个方法都会有方法名、返回值、访问控制符等基本信息,也有具体代码信息,包括运行这段代码可能用到的栈大小、局部变量表大小、入参大小,当然最大一块是具体代码code了,是由一系列的JVM指令构成,这些指令进一步会被java解释器翻译成平台相关的指令供CPU执行,这样就算是在运行我们自己写的代码了。
这里是比较重要的地方,因为我们碰到代码异常执行的时候,或者后面我们分析Java并发编程的时候,就需要看一下JVM这一层是如何执行我们的代码的。
最后还有一个源码与指令偏移之间的对应关系。当我们程序crash的时候,JVM会给出一个异常堆栈,堆栈中可以看到源码的行号,这样便于我们定位问题,其背后的原理就在这里。
E、57
简单表明这个字节码来自的源文件名字。
2. 奇怪的地方
不知道大家有没有注意到上述字节码一些奇怪的地方。
A、在我们的源码中只定义了一个方法say(),但是在字节码中有两个方法,一个方法的名字跟类名一样,是public的,没有返回值。
是的,没错!
这个就是类的默认构造方法,学习java基本知识的时候,都会提到如果程序员不显示指定构造函数的话,那么java会默认给一个实现,通过分析字节码文件,这里就算是实锤了。
B、代码中我们增加了一个注释,但是在JVM中没有发现,也就是说注释是给人看的,机器只看代码,所以不用怕注释写的多影响了执行效率,这个是完全没有的事情。
C、我们源码只有短短的14行,而伪反编译出来字节码有57行,虽然这两个行数不能直接相比,但是也从另外一个侧面反映出编译之后代码膨胀的问题,也说明还好大家是在写高级语言,要是写底层代码的话,工作量不知多了多少倍。
3. 结语
通过字节码文件我们能够清晰的看到我们的源码在底层是如何执行的,但是要了解它们是如何执行的,还需要对每个模块进行详细分析,后面会通过code模块来跟大家一起过一下每个模块的任务,以及写的代码、跳转、关键字等是如何在底层发挥作用的。
顺手点赞心情好,不脱发哦~