点击上方“阿拉奇学Java”,选择“置顶或者星标
 每天早晨0728分, 与你相约!

推荐阅读| 为什么很多SpringBoot开发者放弃了Tomcat,选择了Undertow

背景

前不久《深入理解Java虚拟机》第三版发布了,赶紧买来看了看新版的内容,这本书更新了很多新版本虚拟机的内容,还对以前的部分内容进行了重构,还是值得去看的。本着复习和巩固的态度,我决定来编译一个简单的类文件来分析Java的字节码内容,来帮助理解和巩固Java字节码知识,希望也对阅读本文的你有所帮助。

说明:本次采用的环境是 OpenJdk12。

编译“1+1”代码

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

 1package top.luozhou.test;23/**4 * @description:5 * @author: luozhou6 * @create: 2019-12-25 21:287 **/8public class TestJava {9    public static void main(String[] args) {
10        int a=1+1;
11        System.out.println(a);
12    }
13}

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

 1  Compiled from "TestJava.java"2public class top.luozhou.test.TestJava3  minor version: 04  major version: 565  flags: ACC_PUBLIC, ACC_SUPER6Constant pool:7   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V8   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream;9   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(I)V
10   #4 = Class              #19            // top/luozhou/test/TestJava
11   #5 = Class              #20            // java/lang/Object
12   #6 = Utf8               <init>
13   #7 = Utf8               ()V
14   #8 = Utf8               Code
15   #9 = Utf8               LineNumberTable
16  #10 = Utf8               main
17  #11 = Utf8               ([Ljava/lang/String;)V
18  #12 = Utf8               SourceFile
19  #13 = Utf8               TestJava.java
20  #14 = NameAndType        #6:#7          // "<init>":()V
21  #15 = Class              #21            // java/lang/System
22  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
23  #17 = Class              #24            // java/io/PrintStream
24  #18 = NameAndType        #25:#26        // println:(I)V
25  #19 = Utf8               top/luozhou/test/TestJava
26  #20 = Utf8               java/lang/Object
27  #21 = Utf8               java/lang/System
28  #22 = Utf8               out
29  #23 = Utf8               Ljava/io/PrintStream;
30  #24 = Utf8               java/io/PrintStream
31  #25 = Utf8               println
32  #26 = Utf8               (I)V
33{
34  public top.luozhou.test.TestJava();
35    descriptor: ()V
36    flags: ACC_PUBLIC
37    Code:
38      stack=1, locals=1, args_size=1
39         0: aload_0
40         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
41         4: return
42      LineNumberTable:
43        line 8: 0
44
45  public static void main(java.lang.String[]);
46    descriptor: ([Ljava/lang/String;)V
47    flags: ACC_PUBLIC, ACC_STATIC
48    Code:
49      stack=2, locals=2, args_size=1
50         0: iconst_2
51         1: istore_1
52         2: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
53         5: iload_1
54         6: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
55         9: return
56      LineNumberTable:
57        line 10: 0
58        line 11: 2
59        line 12: 9
60}

解析字节码

基础信息

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

1 minor version: 0 次版本号,为0表示未使用
2 major version: 56 主版本号,56表示jdk12,表示只能运行在jdk12版本以及之后的虚拟机中
3
4flags: ACC_PUBLIC, ACC_SUPER

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

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

常量池

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

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

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

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

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

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

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

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

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

1#4 = Class              #19
2#19 = Utf8               top/luozhou/test/TestJava

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

构造方法信息

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

 1public top.luozhou.test.TestJava();2    descriptor: ()V3    flags: ACC_PUBLIC4    Code:5      stack=1, locals=1, args_size=16         0: aload_07         1: invokespecial #1                  // Method java/lang/Object."<init>":()V8         4: return9      LineNumberTable:
10        line 8: 0

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 这个参数而已。

1         0: aload_0
2         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
3         4: return
4    LineNumberTable:
5        line 8: 0

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

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

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

return:调用完毕方法结束。

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

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

main 方法信息

 1public static void main(java.lang.String[]);2    descriptor: ([Ljava/lang/String;)V3    flags: ACC_PUBLIC, ACC_STATIC4    Code:5      stack=2, locals=2, args_size=16         0: iconst_27         1: istore_18         2: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;9         5: iload_1
10         6: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
11         9: return
12      LineNumberTable:
13        line 10: 0
14        line 11: 2
15        line 12: 9

有了之前构造方法的分析,我们接下来分析 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 给变量,而不是进行加法运算,从而优化了我们的代码,提搞了执行效率。

作者:木木匠
链接:https://juejin.im/post/5e040c7151882512641679ed

看到这里啦,说明你对这篇文章感兴趣,帮忙转发一下或者点击文章右下角在看。感谢啦!关注公众号,回复「进群」即可进入无广告技术交流群。同时送上250本电子书+学习视频作为见面礼!有你想看的精彩 为什么很多SpringBoot开发者放弃了Tomcat,选择了Undertow爱奇艺的 "数据库" 选型到底有多牛逼?
Spring Cloud Greenwich 最后一个计划版本发布!
面试了一个 46 岁程序员,思绪万千
(漫画)一只蝙蝠的自述在朋友圈火了:千万不要再吃野味了!
程序员到新公司后第一周该干些什么?不该干些什么?
(附示例代码)HTTP客户端连接,选择HttpClient还是OkHttp?

武汉加油!中国加油! 

NB会玩,从 1+1=2 来理解 Java 字节码!相关推荐

  1. 不会玩游戏的程序员不是好作家,《深入理解Java虚拟机》周志明来了!

    嘉宾:周志明.杨福川 采访.撰文:Satoh_AI 这次采访起源来自于我和豆瓣的一位读者有同样的好奇心,为什么网上搜不到周志明老师的更多信息?为什么"80后玩家"可以把本本书都维持 ...

  2. 12月最新玩法,月老盲盒微信小程序源码

    今天带来一款全新的换密交友盲盒小程序源码,其实也不能说是盲盒吧,只是部分东西隐藏起来让你猜!! 释放心底的激情,每个人的心中都有属于自己的小秘密,在这里你可以尽情的说出自己的小秘密: 尽情的和别人交换 ...

  3. Html5在线小游戏 在线玩压扁小鸟(flyBird)游戏源码

    这个源码无需后台上传服务器,直接在线即可使用. 该游戏源码是基于HTML5和JavaScript开发的,运行在浏览器中,使得用户能够方便地进行游戏,而且不需要进行任何安装和下载操作.想要玩游戏的用户只 ...

  4. fpga项目开发实例_深入浅出玩转FPGA书+视频教程:35课时+源码

    欢迎FPGA工程师加入官方微信技术群 点击蓝字关注我们FPGA之家-中国最好最大的FPGA纯工程师社群 <深入浅出玩转FPGA(第3版)>收集整理了作者在FPGA项目实践中的经验点滴.书中 ...

  5. 带看板娘玩法指导的贪吃蛇小游戏源码

    介绍: 左下角带看板娘插件,给玩家实时提示和介绍游戏玩法. 上下左右控制方向,数字0暂停,E加速,Q 减速,回车自动/手动切换. 网盘下载地址: http://kekewl.org/bJSm3CqBo ...

  6. 玩吧!北京!招人!Java!

    我们!北京!招人!Java! 我们是特别欢乐的公司(真的是特别欢乐,游戏趴全公司一起吃喝玩) 我们是游戏社交公司(做的都是比较有意思的社交游戏,可以在线匹配,也可以线下邀约) 我们中午管饭,而且吃的挺 ...

  7. 百亿外卖CPS市场该怎么玩?(附公众号小程序裂变源码及搭建教程)

    淘客圈子里面,近半年来,最火热的项目之一,当属外卖CPS了. 刚好前不久也有几个村民来问我最近铺天盖地的外卖CPS是什么,所以借此机会,村长和大家详情的说明一下. 我主要会分为以下四个部分来展开,感兴 ...

  8. c语言转fpga原理,要想玩转FPGA,就必须理解FPGA内部的工作原理-可编程逻辑-与非网...

    FPGA(Field-Program mable Gate Array),即现场可编程门阵列,它是在 PAL.GAL.CPLD 等可编程器件的基础上进一步发展的产物.它是作为专用集成电路(ASIC)领 ...

  9. 新换密交友玩法月老盲盒微信小程序源码_支持分销支付等功能

    简介: 今天带来一款全新的换密交友盲盒小程序源码,其实也不能说是盲盒吧,只是部分东西隐藏起来让你猜!! 释放心底的激情,每个人的心中都有属于自己的小秘密,在这里你可以尽情的说出自己的小秘密: 尽情的和 ...

最新文章

  1. 快排递归非递归python_Python递归神经网络终极指南
  2. 隐私泄露无孔不入?扫地机器人已成新型“窃听器”,小米Roborock“躺枪”
  3. 能量分析攻击day01
  4. set trans 必须是事务处理的第一个语句_MySQL中特别实用的几种SQL语句送给大家
  5. python返回长度值_Python 文件 truncate() 方法(截断返回截取长度)
  6. 物联网基础知识_联网| 基础知识能力问答 套装1
  7. 计算机应用全能,全能计算助手
  8. 谷歌浏览器安卓_用谷歌服务更安全了,安卓手机可充当物理安全密匙
  9. JavaWEB/JSP 中简单的验证码 springMVC
  10. matlab角点坐标获取,MatLab角点检测(harris经典程序) | 学步园
  11. [素数拓展] 质因数的个数 [2007年清华大学计算机研究生机试真题]
  12. 【数据库】E-R图向关系模型转换的规则
  13. System Toolkit for Mac(系统维护工具)
  14. 分布式系统常见的事务处理机制
  15. 自定义cobbler安装系统菜单界面
  16. 如何有效提升软件测试质量?
  17. 天网防火墙Athena 2006正式发布
  18. 关于用友华表Cell插件代码
  19. 2. 数理统计---样本分布
  20. 图形化开发(九)01-Three.js之案例——王者荣耀demo制作

热门文章

  1. Mysql数据库复习大纲
  2. VC++ - 各种DC及DC资源释放
  3. 技术分享|Flow接入节点(Access node)重构介绍
  4. get 请求不读IE缓存
  5. 个推与APICloud达成合作,实现企业精准推送
  6. 校园O2O商铺平台-商品类别模块
  7. 【医学+深度论文:F19】Integrating holistic and local deep features for glaucoma classification
  8. windows快捷键(转)
  9. JS-求三个数中的最大数
  10. 开学季真无线蓝牙耳机怎么选?品质好的蓝牙耳机推荐