编译“1+1”代码

首先我们需要写个简单的小程序,1+1的程序,学习就要从最简单的1+1开始,代码如下:

写好java类文件后,首先执行命令javac TestJava.java 编译类文件,生成TestJava.class。 然后执行反编译命令javap -verbose TestJava,字节码结果显示如下:

Compiled from "TestJava.java"

public class top.luozhou.test.TestJava

minor version: 0

major version: 56

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #5.#14 // java/lang/Object."<init>":()V

#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;

#3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V

#4 = Class #19 // top/luozhou/test/TestJava

#5 = Class #20 // java/lang/Object

#6 = Utf8 <init>

#7 = Utf8 ()V

#8 = Utf8 Code

#9 = Utf8 LineNumberTable

#10 = Utf8 main

#11 = Utf8 ([Ljava/lang/String;)V

#12 = Utf8 SourceFile

#13 = Utf8 TestJava.java

#14 = NameAndType #6:#7 // "<init>":()V

#15 = Class #21 // java/lang/System

#16 = NameAndType #22:#23 // out:Ljava/io/PrintStream;

#17 = Class #24 // java/io/PrintStream

#18 = NameAndType #25:#26 // println:(I)V

#19 = Utf8 top/luozhou/test/TestJava

#20 = Utf8 java/lang/Object

#21 = Utf8 java/lang/System

#22 = Utf8 out

#23 = Utf8 Ljava/io/PrintStream;

#24 = Utf8 java/io/PrintStream

#25 = Utf8 println

#26 = Utf8 (I)V

{

public top.luozhou.test.TestJava();

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 8: 0

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: iconst_2

1: istore_1

2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

5: iload_1

6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V

9: return

LineNumberTable:

line 10: 0

line 11: 2

line 12: 9

}

解析字节码

1.基础信息

上述结果删除了部分不影响解析的冗余信息,接下来我们便来解析字节码的结果。

minor version: 0 次版本号,为0表示未使用

major version: 56 主版本号,56表示jdk12,表示只能运行在jdk12版本以及之后的虚拟机中

flags: ACC_PUBLIC, ACC_SUPER

ACC_PUBLIC:这就是一个是否是public类型的访问标志。

ACC_SUPER: 这个falg是为了解决通过 invokespecial 指令调用 super 方法的问题。可以将它理解成 Java 1.0.2 的一个缺陷补丁,只有通过这样它才能正确找到 super 类方法。从 Java 1.0.2 开始,编译器始终会在字节码中生成 ACC_SUPER 访问标识。感兴趣的同学可以点击这里来了解更多。

2.常量池

接下来,我们将要分析常量池,你也可以对照上面整体的字节码来理解。

这是一个方法引用,这里的#5表示索引值,然后我们可以发现索引值为5的字节码如下

它表示这是一个Object类,同理#14指向的是一个"<init>":()V表示引用的是初始化方法。

上面这段表示是一个字段引用,同样引用了#15和#16,实际上引用的就是java/lang/System类中的PrintStream对象。其他的常量池分析思路是一样的,鉴于篇幅我就不一一说明了,只列下其中的几个关键类型和信息。

NameAndType:这个表示是名称和类型的常量表,可以指向方法名称或者字段的索引,在上面的字节码中都是表示的实际的方法。

Utf8:我们经常使用的是字符编码,但是这个不是只有字符编码的意思,它表示一种字符编码是Utf8的字符串。它是虚拟机中最常用的表结构,你可以理解为它可以描述方法,字段,类等信息。 比如:

这里表示#4这个索引下是一个类,然后指向的类是#19,#19是一个Utf8表,最终存放的是top/luozhou/test/TestJava,那么这样一连接起来就可以知道#4位置引用的类是top/luozhou/test/TestJava了。

3.构造方法信息

接下来,我们分析下构造方法的字节码,我们知道,一个类初始化的时候最先执行它的构造方法,如果你没有写构造方法,系统会默认给你添加一个无参的构造方法。

descriptor: ()V :表示这是一个没有返回值的方法。

flags: ACC_PUBLIC:是公共方法。

stack=1, locals=1, args_size=1 :表示栈中的数量为1,局部变量表中的变量为1,调用参数也为1。

这里为什么都是1呢?这不是默认的构造方法吗?哪来的参数?其实Java语言有一个潜规则:在任何实例方法里面都可以通过this来访问到此方法所属的对象。而这种机制的实现就是通过Java编译器在编译的时候作为入参传入到方法中了,熟悉python语言的同学肯定会知道,在python中定义一个方法总会传入一个self的参数,这也是传入此实例的引用到方法内部,Java只是把这种机制后推到编译阶段完成而已。所以,这里的1都是指this这个参数而已。

经过上面这个分析对于这个构造方法表达的意思也就很清晰了。

aload_0:表示把局部变量表中的第一个变量加载到栈中,也就是this。

invokespecial:直接调用初始化方法。

return:调用完毕方法结束。

LineNumberTable:这是一个行数的表,用来记录字节码的偏移量和代码行数的映射关系。line 8: 0表示,源码中第8行对应的就是偏移量0的字节码,因为是默认的构造方法,所以这里并无法直观体现出来。

另外这里会执行Object的构造方法是因为,Object是所有类的父类,子类的构造要先构造父类的构造方法。

4.main方法信息

有了之前构造方法的分析,我们接下来分析main方法也会熟悉很多,重复的我就略过了,这里重点分析code部分。

stack=2, locals=2, args_size=1:这里的栈和局部变量表为2,参数还是为1。这是为什么呢?因为main方法中声明了一个变量a,所以局部变量表要加一个,栈也是,所以他们是2。那为什么args_size还是1呢?你不是说默认会把this传入的吗?应该是2啊。注意:之前说的是在任何实例方法中,而这个main方法是一个静态方法,静态方法直接可以通过类+方法名访问,并不需要实例对象,所以这里就没必要传入了。

0: iconst_2:将int类型2推送到栈顶。

1: istore_1:将栈顶int类型数值存入第二个本地变量。

2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;:获取PrintStream类。

5: iload_1: 把第二个int型本地变量推送到栈顶。

6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V:调用println方法。这里的println方法就会把栈顶的元素作为自己的入参来执行,最终也会输出2。

9: return:调用完毕结束方法。

这里的LineNumberTable是有源码的,我们可以对照下我前面描述是否正确:

line 10: 0: 第10行表示0: iconst_2字节码,这里我们发现编译器直接给我们计算好了把2推送到栈顶了。

line 11: 2:第11行源码对应的是2: getstatic 获取输出的静态类PrintStream。

line 12: 9:12行源码对应的是return,表示方法结束。

这里我也画了一个动态图片来演示main方法执行的过程,希望能够帮助你理解:

总结

这篇文章我从1+1的的源码编译开始,分析了生成后的Java字节码,包括类的基本信息,常量池,方法调用过程等,通过这些分析,我们对Java字节码有了比较基本的了解,也知道了Java编译器会把优化手段通过编译好的字节码体现出来,比如我们的1+1=2,字节码字节赋值一个2给变量,而不是进行加法运算,从而优化了我们的代码,提搞了执行效率。

从1+1=2来理解Java字节码从1+1=2来理解Java字节码的更多相关文章

  1. java入门时的一些基本概念的理解(j2ee,j2se,j2me,jdk,sdk,jre,jvm,跨平台)

    首先声明,这篇文章是从网上粘贴过来的.原文地址是:http://www.cnblogs.com/wangaohui/archive/2012/11/28/2791999.html.感觉写的很好,所以粘 ...

  2. Java序列化机制和原理及自己的理解

    Java序列化算法透析 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是一种将这些字节重建成一个对象的过程.Java序列化API提供一 ...

  3. Java泛型中的类型擦除机制简单理解

    Java的泛型是JDK1.5时引入的.下面只是简单的介绍,不做深入的分析. Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首 ...

  4. OpenJDK源码研究笔记(八)-详细解析如何读取Java字节码文件(.class)

    在上一篇OpenJDK源码研究笔记(七)–Java字节码文件(.class)的结构中,我们大致了解了Java字节码文件的结构. 本篇详细地介绍了如何读取.class文件的大部分细节. 1.构造文件  ...

  5. Java内存区域与内存溢出异常——深入理解Java虚拟机 笔记一

    Java内存区域 对比与C和C++,Java程序员不需要时时刻刻在意对象的创建和删除过程造成的内存溢出.内存泄露等问题,Java虚拟机很好地帮助我们解决了内存管理的问题,但深入理解Java内存区域,有 ...

  6. 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!

    Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...

  7. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  8. Java随谈(六)## 我们真的理解 Java 里的整型吗?

    我们真的理解 Java 里的整型吗 整型是我们日常生活中最常用到的基础数据类型,看这篇文章之前,我想问: 我们真的像自己认为的那么理解 Java 内的整型吗? 也许看完本篇文章你就有自己的答案. C ...

  9. java学习笔记--this 关键字的理解

    彻底理解this 关键字的含义 this关键字再java里面是一个我认为非常不好理解的概念,:)也许是太笨的原因 this 关键字的含义:可为以调用了其方法的那个对象生成相应的句柄. 怎么理解这段话呢 ...

随机推荐

  1. android之布局优化

    android中提供了<include />.<merge />.<ViewStub />三种优化布局. 1.<include /> <inclu ...

  2. 【技巧】使用xshell和xftp连接centos连接配置

    说明:xshell用来执行指令,xftp用来上传和下载文件. ① 这是xshell连接属性: ②.这是xftp连接属性 附件:这里给个xshelll和xftp的免安装的破解版本地址.侵删. 度娘链接: ...

  3. thinkphp之独立日志(tp5.1)

    为了便于分析,File类型的日志还支持设置某些级别的日志信息单独文件记录,以error类型的日志为例,例如: 1.在log.php 中配置 'apart_level' => [ 'error' ...

  4. mongodb 在PHP中常见问题及解决方法

    1.$in needs an array 解决:查询用到in操作的时候,说in操作对应的不是我一个数组,或者数组索引不是以0开始的 方法:array_values重新生成一个索引为0开始的数组即可 $ ...

  5. 序列化-JDK自带Serializable

    如下代码示例:实现了Serializable接口(强制)的类,可以通过ObjectOutputStream的writeObject()方法转为字节流. 字节流通过ObjectInputStream的r ...

  6. 数据结构之Set | 让我们一块来学习数据结构

    数组(列表).栈.队列和链表这些顺序数据结构对你来说应该不陌生了.现在我们要学习集合,这是一种不允许值重复的顺序数据结构.我们将要学到如何创建集合这种数据结构,如何添加和移除值,如何搜索值是否存在.你 ...

  7. 【一】kubernetes学习笔记-Pod概念

    一.Pod 控制器类型 Pod概念 当一个 Pod 创建后,Pause 容器就会随着 Pod 启动,只要是有 Pod,Pause 容器就要被启动. 在同一个 Pod 里面的容器不能出现端口冲突,否则这 ...

  8. java面试一日一题:java中垃圾回收算法有哪些

    问题:请讲下在java中有哪些垃圾回收算法 分析:该问题主要考察对java中垃圾回收的算法以及使用场景 回答要点: 主要从以下几点去考虑, 1.GC回收算法有哪些 2.每种算法的使用场景 3.基于垃圾 ...

  9. 索引器和ref、out关键字

    这节讲三个小知识:索引器.ref.out. 索引器: 在一个类中,我们可以定义一个索引器,它可以让我们在外部像访问数组元素一样访问类的属性成员. 索引器的定义就像定义属性一样,只不过名称为this,后 ...

  10. MindSpore模型验证

    技术背景 在前面一篇博客中,我们介绍了MindSpore在机器学习过程中保存和加载模型的方法.这种将模型存储为静态文件的做法,使得我们可以更灵活的使用训练出来的模型,比如用于各种数据集的验证,或者是迁 ...