java字节码 学了有用吗,为什么要推荐大家学习字节码?

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

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

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

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

1.1 从生活的角度来讲

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

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

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

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

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

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

想想很多同学不愿意改变方法,只想多努力的这种思维,看似很对,其实.....

1.2 学习Java的角度

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

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

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

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

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

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

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

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

话说很多人人刚从eclipse 换用 IDEA的时候也会吐槽 IDEA “难用”,不知道用了很长时间之后,你是否还回得去呢?还乐意用 eclipse呢?

当你在这个阶段时,你再看看那些还不愿意换IDEA,说 IDEA难用的人,是什么心情?

2.2 为什么要学习字节码?

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

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

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

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

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

3.1 例子

3.1.1 语法糖public class ForEachDemo {

public static void main(String[] args) {

List data = new ArrayList<>();

data.add("a");

data.add("b");

for (String str : data) {

System.out.println(str);

}

}

}

编译: javac ForEachDemo.java

反汇编:javap -c ForEachDemopublic 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."":()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."":()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

}

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

看不懂字节码指令没关系,看看右侧的注释“猜”也能猜出来吧。

刚开始学的时候一定不要对自己要求太高,先看个大概即可。

3.1.2 读源码中的一个优化的例子

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

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#startWebServerprivate WebServer startWebServer() {

WebServer webServer = this.webServer;

if (webServer != null) {

webServer.start();

}

return webServer;

}

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

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

可能有些人读过某些文章有提到(如果别人没提到怎么办?为啥学知识总要间接学习呢?),更多的人可能并不能理解有什么优化。

3.2 模拟

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

模仿上述写法的例子:public class LocalDemo {

private List data = new ArrayList<>();

public void someMethod(String param) {

List data = this.data;

if (data != null && data.size() > 0 && data.contains(param)) {

System.out.println(data.indexOf(param));

}

}

}

编译:javac LocalDemo.java

反汇编: javap -c LocalDemopublic 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."":()V

4: aload_0

5: new           #2                  // class java/util/ArrayList

8: dup

9: invokespecial #3                  // Method java/util/ArrayList."":()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

}

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

AAffA0nNPuCLAAAAAElFTkSuQmCC

直接使用成员变量的例子:public class ThisDemo {

private List 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 ThisDemopublic 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."":()V

4: aload_0

5: new           #2                  // class java/util/ArrayList

8: dup

9: invokespecial #3                  // Method java/util/ArrayList."":()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

}

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

AAffA0nNPuCLAAAAAElFTkSuQmCC

大家也可以通过  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的一些语法,甚至帮助大家学习源码。

如果能够熟练使用,可以学习很多自己一知半解的知识。

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

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

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

专栏中也大量运用了读源码、分析字节码来学习的方法。

想了解更多《手册》详解的更多内容,想学习开发中比较有帮助的插件,更多开发和避坑技巧,少走弯路,请关注《阿里巴巴Java 开发手册》详解专栏。

如果本文或者专栏对你有帮助,欢迎介绍给身边的同学、朋友,你的支持是我持续创作的最大动力。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值