配套视频:

为什么推荐大家学习Java字节码

https://www.bilibili.com/video/av77600176/

一、背景

本文主要探讨:为什么要学习 JVM 字节码?

可能很多人会觉得没必要,因为平时开发用不到,而且不学这个也没耽误学习。

但是这里分享一点感悟,即人总是根据自己已经掌握的知识和技能来解决问题的。

这里有个悖论,有时候你觉得有些技术没用恰恰是因为你没有熟练掌握它,遇到可以使用它的场景你根本想不到用

1.1 从生活的角度来讲

如果你是一个非计算机专业的学生,你老师给你几张图书的拍照,大概3000字,让你打印成文字。

你打开电脑,噼里啪啦一顿敲,搞了一下午干完了。

如果你知道语音输入,那么你可能采用语音输入的方式,30分钟搞定。

如果你了解 OCR 图片文字识别,可能 5 分钟搞定。

不同的方法,带来的效果完全不同。然而最可怕的是,你不会语音输入或者OCR你不会觉得自己少了啥。

OCR识别绝对不是你提高点打字速度可以追赶上的。

1.2 学习Java的角度

很多人学习知识主要依赖百度,依赖博客,依赖视频和图书,而且这些资料质量参差不齐,而且都是别人理解之后的结果。

比如你平时不怎么看源码,那么你就很少能将源码作为你学习的素材,只能依赖博客、图书、视频等。

如果你平时喜欢看源码,你会对源码有自己的理解,你会发现源码对你的学习有很多帮助。

如果你平时不怎么用反编译和反汇编,那么你更多地只能依赖源码,依赖调试等学习知识,而不能从字节码层面来学习和理解知识。

当你慢慢熟练读懂虚拟机指令,你会发现你多了一个学习知识的途径。

二、为什么要学习字节码

2.1 人总是不愿意离开舒适区的

很多人在学习新知识时,总是本能地抵触。会找各种理由不去学,“比如暂时用不到”,“学了没啥用”,“以后再说”。

甚至认为这是在浪费时间。

2.2 为什么要学习字节码?

最近学习了一段时间 JVM 字节码的知识,虽然不算精通,但是读字节码起来已经不太吃力。

为什么推荐学习字节码是因为它可以从比源码更深的层面去学习 Java 相关知识。

虽然不可能所有问题都用字节码的知识来解决,但是它给你一个学习的途径。

比如通过字节码的学习你可以更好地理解 Java中各种语法和语法糖背后的原理,更好地理解多态等语言特性。

三、举例

本文举一个简单的例子,来说明学习字节码的作用。

3.1  例子

3.1.1 语法糖

public class ForEachDemo {public static void main(String[] args) {List<String> data = new ArrayList<>();data.add("a");data.add("b");for (String str : data) {System.out.println(str);}}
}

编译: javac ForEachDemo.java

反汇编:javap -c ForEachDemo

public class com.imooc.basic.learn_source_code.local.ForEachDemo {public com.imooc.basic.learn_source_code.local.ForEachDemo();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: new           #2                  // class java/util/ArrayList3: dup4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V7: astore_18: aload_19: ldc           #4                  // String a11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z16: pop17: aload_118: ldc           #6                  // String b20: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z25: pop26: aload_127: invokeinterface #7,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;32: astore_233: aload_234: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z39: ifeq          6242: aload_243: invokeinterface #9,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;48: checkcast     #10                 // class java/lang/String51: astore_352: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;55: aload_356: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V59: goto          3362: return
}

我们可以清晰地看到foreach 循环底层用到了迭代器实现,甚至可以逆向脑补出对应的Java源码(大家可以尝试根据字节码写出等价的源码)。

3.1.2 读源码遇到的一个问题

我们在读源码时经常会遇到类似下面的这种写法:

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServer

   private WebServer startWebServer() {WebServer webServer = this.webServer;if (webServer != null) {webServer.start();}return webServer;}

在函数中声明一个和成员变量同名的局部变量,然后将成员变量赋值给局部变量,再去使用。

看似很小的细节,隐含着一个优化思想。

可能有些人读过某些文章有提到(可是为什么我们总得看到一个文章会一个知识?如果没看到怎么办?),更多的人可能并不能理解有什么优化。

3.2 模拟

普通的语法糖这里就不做过多展开,重点讲讲第二个优化的例子。

模仿上述写法的例子

public class LocalDemo {private List<String> data = new ArrayList<>();public void someMethod(String param) {List<String> data = this.data;if (data != null && data.size() > 0 && data.contains(param)) {System.out.println(data.indexOf(param));}}}

编译:javac LocalDemo.java

反汇编: javap -c LocalDemo

public class com.imooc.basic.learn_source_code.local.LocalDemo {public com.imooc.basic.learn_source_code.local.LocalDemo();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: new           #2                  // class java/util/ArrayList8: dup9: invokespecial #3                  // Method java/util/ArrayList."<init>":()V12: putfield      #4                  // Field data:Ljava/util/List;15: returnpublic void someMethod(java.lang.String);Code:0: aload_01: getfield      #4                  // Field data:Ljava/util/List;4: astore_25: aload_26: ifnull        419: aload_210: invokeinterface #5,  1            // InterfaceMethod java/util/List.size:()I15: ifle          4118: aload_219: aload_120: invokeinterface #6,  2            // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z25: ifeq          4128: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;31: aload_232: aload_133: invokeinterface #8,  2            // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I38: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V41: return
}

此时 局部变量表中 0 为 this , 1 为 param  2 为 局部变量  data

直接使用成员变量的例子


public class ThisDemo {private List<String> data = new ArrayList<>();public void someMethod(String param) {if (data != null && data.size() > 0 && data.contains(param)) {System.out.println(data.indexOf(param));}}
}

编译:javac ThisDemo.java

反汇编: javap -c ThisDemo

public class com.imooc.basic.learn_source_code.local.ThisDemo {public com.imooc.basic.learn_source_code.local.ThisDemo();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: new           #2                  // class java/util/ArrayList8: dup9: invokespecial #3                  // Method java/util/ArrayList."<init>":()V12: putfield      #4                  // Field data:Ljava/util/List;15: returnpublic void someMethod(java.lang.String);Code:0: aload_01: getfield      #4                  // Field data:Ljava/util/List;4: ifnull        487: aload_08: getfield      #4                  // Field data:Ljava/util/List;11: invokeinterface #5,  1            // InterfaceMethod java/util/List.size:()I16: ifle          4819: aload_020: getfield      #4                  // Field data:Ljava/util/List;23: aload_124: invokeinterface #6,  2            // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z29: ifeq          4832: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;35: aload_036: getfield      #4                  // Field data:Ljava/util/List;39: aload_140: invokeinterface #8,  2            // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I45: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V48: return
}

此时局部变量表只有两个,即  this 和  param。

大家也可以通过  javap -c -v 来查看更详细信息,本例截图中用到 IDEA 插件为jclasslib bytecode viewer,感兴趣参考我的另外一篇对该工具的介绍博文:《IDEA字节码学习查看神器jclasslib bytecode viewer介绍》。

3.3 分析

通过源码其实我们并不能很好的理解到底优化了哪里。

我们分别对两个类进行编译和反汇编后可以清晰地看到:第一个例子代码多了一行,反而反编译后的字节码更短

第二个例子反编译后的字节码比第一个例子长在哪里呢?

我们发现主要多在:getfield      #4                  // Field data:Ljava/util/List; 这里

即每次获取 data对象都要先  aload_0 然后再 getfield 指令获取。

第一个例子通过 astore_2 将其存到了局部变量表中,每次用直接 aload_2 直接从局部变量表中加载到操作数栈。

从而不需要每次都从 this 对象中获取这个属性,因此效率更高。

这种思想有点像写代码中常用的缓存,即将最近要使用的数据先查一次缓存起来,使用时优先查缓存。

本质上体现了操作系统中的时间局部性和空间局部性的概念(不懂的话翻下书或百度下)

因此通过字节码的分析,通过联系实际的开发经验,通过联系专业知识,这个问题我们就搞明白了

另外也体现了用空间换时间的思想

知识只有能贯穿起来,理解的才能更牢固。

此处也体现出专业基础的重要性。

另外知识能联系起来、思考到本质,理解才能更深刻,记忆才能更牢固,才更有可能灵活运用。

四、总结

这只是其中一个非常典型的例子,学习 JVM 字节码能够给你一个不一样的视角,让你多一个学习的途径。

可能很多人说自己想学但是无从下手,这里推荐大家先看《深入理解Java虚拟机》,然后结合《Java虚拟机规范》,平时多敲一下 javap 指令,慢慢就熟悉了,另外强力推荐jclasslib bytecode viewer插件,该插件可以点击指令跳转到 Java虚拟机规范对该指令的介绍的部分,对学习帮助极大。

很多人可能会说,学这个太慢。

的确,急于求成怎么能学的特别好呢?厚积才能薄发,耐不住寂寞怎么能学有所成呢。

本文通过这其中一个例子让大家理解,JVM字节码可以帮助大家理解Java的一些语法(篇幅有限,而且例子太多,这里就不给出了,感兴趣的同学自己尝试),甚至帮助大家学习源码。

试想一下,如果你认为学习字节码无用,甚至你都不了解,你怎么可能用它来解决问题呢?

你所掌握的知识帮助你成长由限制了你的成长,要敢于突破舒适区,给自己更多的成长机会。

-------------------

欢迎点赞、评论、转发,你的鼓励,是我创作的动力。

为什么要推荐大家学习字节码?相关推荐

  1. JVM学习-字节码指令

    目录 1.入门 2 javap 工具 3 图解方法执行流程 3.1.原始 java 代码 3.2.编译后的字节码文件 3.3.常量池载入运行时常量池 3.4.方法字节码载入方法区 3.5.main 线 ...

  2. JAVA中的ret是啥意思,为什么JSR / RET不推荐使用Java字节码?

    Does anyone know why the JSR/RET bytecode pair is deprecated in Java 6? The only meaningful explanat ...

  3. JVM学习笔记(Ⅰ):Class类文件结构解析(带你读懂Java字节码,这一篇就够了)

    JVM学习笔记(Ⅰ):Class类文件结构解析,带你读懂Java字节码 前言:本文属于博主个人的学习笔记,博主也是小白.如果有不对的地方希望各位帮忙指出.本文主要还是我的学习总结,因为网上的一些知识分 ...

  4. idea 如何看bytecode_IDEA字节码学习查看神器介绍

    一.背景 很多人想学习Java反汇编后的字节码,但是一方面缺乏好的资料,另外一方面缺乏好的工具. 关于资料大家可以看 <Java虚拟机规范>.<深入理解Java虚拟机>,还可以 ...

  5. JVM学习笔记之字节码指令集

    目录 背景 概述 执行模型 字节码与数据类型 指令分类 加载与存储指令 再谈操作数栈与局部变量表 局部变量压栈指令 常量入栈指令 出栈装入局部变量表指令 算术指令 所有算术指令 比较指令的说明 类型转 ...

  6. 一款Java字节码神器-jclasslib bytecode viewer

    一.前言 相信很多学习Java的小伙伴都很好奇编译后的class文件里面的内容到底长什么样?我们都知道class文件里面存储的是字节码,直接打开文件是一堆乱码,所以我们需要一些工具来帮助我们去查看字节 ...

  7. bat 调用class文件_拯救写框架的程序员!用字节码替代反射,实现任意函数调用...

    作者 | 阿里巴巴文娱高级开发工程师 兰摧 技术类别:JAVA,后端技术,中间件开发,框架开发 技术亮点:字节码实现类似反射的功能,速度接近JAVA原生的调用 一.背景 我们在写一些框架或者中间件时, ...

  8. JVM笔记:Java虚拟机的字节码指令详解

    1.字节码 Java能发展到现在,其"一次编译,多处运行"的功能功不可没,这里最主要的功劳就是JVM和字节码了,在不同平台和操作系统上根据JVM规范的定制JVM可以运行相同字节码( ...

  9. 学习笔记:Java虚拟机——JVM内存结构、垃圾回收、类加载与字节码技术

    学习视频来源:https://www.bilibili.com/video/BV1yE411Z7AP Java类加载机制与ClassLoader详解推荐文章:https://yichun.blog.c ...

最新文章

  1. 【洛谷P1186】玛丽卡(断边+最短路)
  2. bzoj4027,[HEOI2015]兔子与樱花
  3. 面试项目亮点_当面试官谈到项目经验的时候,你知道怎么回答吗?怎么反过来控制面试流程?...
  4. 查询所有_学会DSUM函数,轻松搞定所有的数据查询与数据求和
  5. 商汤科技20篇论文入选ICCV 2017,披露最新研究主线
  6. [实战]MVC5+EF6+MySql企业网盘实战(24)——视频列表
  7. gimp中文版教程_Gimp中文经典入门实用教程(合辑).pdf
  8. qdialog修改标题栏图标_qt – 如何在QDockWidget标题栏中显示图标?
  9. python插件安装包_Python的插件安装
  10. JavaScript图片旋转缩放、像素矩阵获取
  11. tl431 输出接104 振荡
  12. 西门子PLC1200 TCP通讯
  13. STM32学习(一)
  14. html添加一条虚线垂直的,【html问题】在网页中添加垂直分割线
  15. ffmpeg ——下载与安装
  16. NVDIA CUDA安装失败解决方案最新版(2021.10.8)
  17. 2008中国商业得意榜与失意榜
  18. Google 中国在首页为 5.12 地震遇难者祈祷
  19. Soul网关源码阅读19-解析sign插件
  20. 初入云计算行业,可以考取哪些云计算证书?

热门文章

  1. 第2章 Python编程基础知识 第2.1节 简单的Python数据类型、变量赋值及输入输出
  2. java打地鼠_Java 实现打地鼠游戏
  3. ExoPlayer拖动进度条时显示视频缩略图
  4. 因为不知道,所以不知道
  5. 护卫神设置public目录(IIS下TP5如何设置运行目录)
  6. [深入理解SSD系列综述 1.3] SSD及固态存储技术半个世纪发展史
  7. 来看看这款简单优雅的实时聊天软件
  8. 协议转换器指示灯的含义
  9. python 路径中的反斜杠转换为正斜杠
  10. 中职学机电一体化好还是计算机好,男生读职校选择什么专业比较好