程序员

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

作者:admin 2021-08-17 我要评论

?配套视频 为什么推荐大家学习Java字节码 https://www.bilibili.com/video/av77600176/ 一、背景 本文主要探讨 为什么要学习 JVM 字节码 可能很多人会觉得没必要...

在说正事之前,我要推荐一个福利:你还在原价购买阿里云、腾讯云、华为云服务器吗?那太亏啦!来这里,新购、升级、续费都打折,能够为您省60%的钱呢!2核4G企业级云服务器低至69元/年,点击进去看看吧>>>)
?配套视频

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

image.gif

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);
}

image.gif

编译 ?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_0
 1: invokespecial #1 // Method java/lang/Object. init :()V
 4: return
 public static void main(java.lang.String[]);
 Code:
 0: new #2 // class java/util/ArrayList
 3: dup
 4: invokespecial #3 // Method java/util/ArrayList. init :()V
 7: astore_1
 8: aload_1
 9: ldc #4 // String a
 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
 16: pop
 17: aload_1
 18: ldc #6 // String b
 20: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
 25: pop
 26: aload_1
 27: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
 32: astore_2
 33: aload_2
 34: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
 39: ifeq 62
 42: aload_2
 43: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
 48: checkcast #10 // class java/lang/String
 51: astore_3
 52: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
 55: aload_3
 56: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
 59: goto 33
 62: return
}

image.gif

我们可以清晰地看到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;
 }

image.gif

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

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

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

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));
}

image.gif

编译 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_0
 1: invokespecial #1 // Method java/lang/Object. init :()V
 4: aload_0
 5: new #2 // class java/util/ArrayList
 8: dup
 9: invokespecial #3 // Method java/util/ArrayList. init :()V
 12: putfield #4 // Field data:Ljava/util/List;
 15: return
 public void someMethod(java.lang.String);
 Code:
 0: aload_0
 1: getfield #4 // Field data:Ljava/util/List;
 4: astore_2
 5: aload_2
 6: ifnull 41
 9: aload_2
 10: invokeinterface #5, 1 // InterfaceMethod java/util/List.size:()I
 15: ifle 41
 18: aload_2
 19: aload_1
 20: invokeinterface #6, 2 // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z
 25: ifeq 41
 28: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
 31: aload_2
 32: aload_1
 33: invokeinterface #8, 2 // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I
 38: invokevirtual #9 // Method java/io/PrintStream.println:(I)V
 41: return
}

image.gif

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

image.gif

直接使用成员变量的例子

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));
}

image.gif

编译 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_0
 1: invokespecial #1 // Method java/lang/Object. init :()V
 4: aload_0
 5: new #2 // class java/util/ArrayList
 8: dup
 9: invokespecial #3 // Method java/util/ArrayList. init :()V
 12: putfield #4 // Field data:Ljava/util/List;
 15: return
 public void someMethod(java.lang.String);
 Code:
 0: aload_0
 1: getfield #4 // Field data:Ljava/util/List;
 4: ifnull 48
 7: aload_0
 8: getfield #4 // Field data:Ljava/util/List;
 11: invokeinterface #5, 1 // InterfaceMethod java/util/List.size:()I
 16: ifle 48
 19: aload_0
 20: getfield #4 // Field data:Ljava/util/List;
 23: aload_1
 24: invokeinterface #6, 2 // InterfaceMethod java/util/List.contains:(Ljava/lang/Object;)Z
 29: ifeq 48
 32: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
 35: aload_0
 36: getfield #4 // Field data:Ljava/util/List;
 39: aload_1
 40: invokeinterface #8, 2 // InterfaceMethod java/util/List.indexOf:(Ljava/lang/Object;)I
 45: invokevirtual #9 // Method java/io/PrintStream.println:(I)V
 48: return
}

image.gif

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

image.gif

大家也可以通过??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的一些语法 篇幅有限 而且例子太多 这里就不给出了 感兴趣的同学自己尝试 甚至帮助大家学习源码。

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

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

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

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




本文转自网络,原文链接:https://developer.aliyun.com/article/787265

版权声明:本文转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本站转载出于传播更多优秀技术知识之目的,如有侵权请联系QQ/微信:153890879删除

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

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

  • 细微之处见真章之JSON格式美化

    细微之处见真章之JSON格式美化

  • -XX:PretenureSizeThreshold的默认值和

    -XX:PretenureSizeThreshold的默认值和

  • MySQL的in和or的效率问题浅析

    MySQL的in和or的效率问题浅析

腾讯云代理商
海外云服务器