我要悄悄学习 Java 字节码指令,在成为技术大佬的路上一去不复返

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

private void load(int age, String name, long birthday, boolean sex) {

System.out.println(age + name + birthday + sex);

}

通过 jclasslib 看一下 load() 方法(4 个参数)的字节码指令。

  • iload_1:将局部变量表中下标为 1 的 int 变量压入操作数栈中。

  • aload_2:将局部变量表中下标为 2 的引用数据类型变量(此时为 String)压入操作数栈中。

  • lload_3:将局部变量表中下标为 3 的 long 型变量压入操作数栈中。

  • iload 5:将局部变量表中下标为 5 的 int 变量(实际为 boolean)压入操作数栈中。

通过查看局部变量表就能关联上了。

2)将常量池中的常量压入操作数栈中

根据数据类型和入栈内容的不同,此类又可以细分为 const 系列、push 系列和 Idc 指令。

const 系列,用于特殊的常量入栈,要入栈的常量隐含在指令本身。

push 系列,主要包括 bipush 和 sipush,前者接收 8 位整数作为参数,后者接收 16 位整数。

Idc 指令,当 const 和 push 不能满足的时候,万能的 Idc 指令就上场了,它接收一个 8 位的参数,指向常量池中的索引。

  • Idc_w:接收两个 8 位数,索引范围更大。

  • 如果参数是 long 或者 double,使用 Idc2_w 指令。

举例来说。

public void pushConstLdc() {

// 范围 [-1,5]

int iconst = -1;

// 范围 [-128,127]

int bipush = 127;

// 范围 [-32768,32767]

int sipush= 32767;

// 其他 int

int ldc = 32768;

String aconst = null;

String IdcString = “沉默王二”;

}

通过 jclasslib 看一下 pushConstLdc() 方法的字节码指令。

  • iconst_m1:将 -1 入栈。范围 [-1,5]。

  • bipush 127:将 127 入栈。范围 [-128,127]。

  • sipush 32767:将 32767 入栈。范围 [-32768,32767]。

  • ldc #6 <32768>:将常量池中下标为 6 的常量 32768 入栈。

  • aconst_null:将 null 入栈。

  • ldc #7 <沉默王二>:将常量池中下标为 7 的常量“沉默王二”入栈。

3)将栈顶的数据出栈并装入局部变量表中

主要是用来给局部变量赋值,这类指令主要以 store 的形式存在。

  • xstore_(x 为 i、l、f、d、a,n 默认为 0 到 3)

  • xstore(x 为 i、l、f、d、a)

明白了 xload_ 和 xload,再看 xstore_ 和 xstore 就会轻松得多,作用反了一下而已。

大家来想一个问题,为什么要有 xstore_ 和 xload_ 呢?它们的作用和 xstore n、xload n 不是一样的吗?

xstore_ 和 xstore n 的区别在于,前者相当于只有操作码,占用 1 个字节;后者相当于由操作码和操作数组成,操作码占 1 个字节,操作数占 2 个字节,一共占 3 个字节。

由于局部变量表中前几个位置总是非常常用,虽然 xstore_<n>xload_<n> 增加了指令数量,但字节码的体积变小了!

举例来说。

public void store(int age, String name) {

int temp = age + 2;

String str = name;

}

通过 jclasslib 看一下 store() 方法的字节码指令。

  • istore_3:从操作数中弹出一个整数,并把它赋值给局部变量表中索引为 3 的变量。

  • astore 4:从操作数中弹出一个引用数据类型,并把它赋值给局部变量表中索引为 4 的变量。

通过查看局部变量表就能关联上了。

02、算术指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈。可以分为两类:整型数据的运算指令和浮点数据的运算指令。

需要注意的是,数据运算可能会导致溢出,比如两个很大的正整数相加,很可能会得到一个负数。但 Java 虚拟机规范中并没有对这种情况给出具体结果,因此程序是不会显式报错的。所以,大家在开发过程中,如果涉及到较大的数据进行加法、乘法运算的时候,一定要注意!

当发生溢出时,将会使用有符号的无穷大 Infinity 来表示;如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。而且所有使用 NaN 作为操作数的算术操作,结果都会返回 NaN。

举例来说。

public void infinityNaN() {

int i = 10;

double j = i / 0.0;

System.out.println(j); // Infinity

double d1 = 0.0;

double d2 = d1 / 0.0;

System.out.println(d2); // NaN

}

  • 任何一个非零的数除以浮点数 0(注意不是 int 类型),可以想象结果是无穷大 Infinity 的。

  • 把这个非零的数换成 0 的时候,结果又不太好定义,就用 NaN 值来表示。

Java 虚拟机提供了两种运算模式

  • 向最接近数舍入:在进行浮点数运算时,所有的结果都必须舍入到一个适当的精度,不是特别精确的结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值接近,将优先选择最低有效位为零的(类似四舍五入)。

  • 向零舍入:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果(类似取整)。

我把所有的算术指令列一下:

  • 加法指令:iadd、ladd、fadd、dadd

  • 减法指令:isub、lsub、fsub、dsub

  • 乘法指令:imul、lmul、fmul、dmul

  • 除法指令:idiv、ldiv、fdiv、ddiv

  • 求余指令:irem、lrem、frem、drem

  • 自增指令:iinc

举例来说。

public void calculate(int age) {

int add = age + 1;

int sub = age - 1;

int mul = age * 2;

int div = age / 3;

int rem = age % 4;

age++;

age–;

}

通过 jclasslib 看一下 calculate() 方法的字节码指令。

  • iadd,加法

  • isub,减法

  • imul,乘法

  • idiv,除法

  • irem,取余

  • iinc,自增的时候 +1,自减的时候 -1

03、类型转换指令

可以分为两种:

1)宽化,小类型向大类型转换,比如 int–>long–>float–>double,对应的指令有:i2l、i2f、i2d、l2f、l2d、f2d。

  • 从 int 到 long,或者从 int 到 double,是不会有精度丢失的;

  • 从 int、long 到 float,或者 long 到 double 时,可能会发生精度丢失;

  • 从 byte、char 和 short 到 int 的宽化类型转换实际上是隐式发生的,这样可以减少字节码指令,毕竟字节码指令只有 256 个,占一个字节。

2)窄化,大类型向小类型转换,比如从 int 类型到 byte、short 或者 char,对应的指令有:i2b、i2s、i2c;从 long 到 int,对应的指令有:l2i;从 float 到 int 或者 long,对应的指令有:f2i、f2l;从 double 到 int、long 或者 float,对应的指令有:d2i、d2l、d2f。

  • 窄化很可能会发生精度丢失,毕竟是不同的数量级;

  • 但 Java 虚拟机并不会因此抛出运行时异常。

举例来说。

public void updown() {

int i = 10;

double d = i;

float f = 10f;

long ong = (long)f;

}

通过 jclasslib 看一下 updown() 方法的字节码指令。

  • i2d,int 宽化为 double

  • f2l, float 窄化为 long

04、对象的创建和访问指令

Java 是一门面向对象的编程语言,那么 Java 虚拟机是如何从字节码层面进行支持的呢?

1)创建指令

数组也是一种对象,但它创建的字节码指令和普通的对象不同。创建数组的指令有三种:

  • newarray:创建基本数据类型的数组

  • anewarray:创建引用类型的数组

  • multianewarray:创建多维数组

普通对象的创建指令只有一个,就是 new,它会接收一个操作数,指向常量池中的一个索引,表示要创建的类型。

举例来说。

public void newObject() {

String name = new String(“沉默王二”);

File file = new File(“无愁河的浪荡汉子.book”);

int [] ages = {};

}

通过 jclasslib 看一下 newObject() 方法的字节码指令。

  • new #13 <java/lang/String>,创建一个 String 对象。

  • new #15 <java/io/File>,创建一个 File 对象。

  • newarray 10 (int),创建一个 int 类型的数组。

2)字段访问指令

字段可以分为两类,一类是成员变量,一类是静态变量(static 关键字修饰的),所以字段访问指令可以分为两类:

  • 访问静态变量:getstatic、putstatic。

  • 访问成员变量:getfield、putfield,需要创建对象后才能访问。

举例来说。

public class Writer {

private String name;

static String mark = “作者”;

public static void main(String[] args) {

print(mark);

Writer w = new Writer();

print(w.name);

}

public static void print(String arg) {

System.out.println(arg);

}

}

通过 jclasslib 看一下 main() 方法的字节码指令。

  • getstatic #2 <com/itwanger/jvm/Writer.mark>,访问静态变量 mark

  • getfield #6 <com/itwanger/jvm/Writer.name>,访问成员变量 name

05、方法调用和返回指令

方法调用指令有 5 个,分别用于不同的场景:

  • invokevirtual:用于调用对象的成员方法,根据对象的实际类型进行分派,支持多态。

  • invokeinterface:用于调用接口方法,会在运行时搜索由特定对象实现的接口方法进行调用。

  • invokespecial:用于调用一些需要特殊处理的方法,包括构造方法、私有方法和父类方法。

  • invokestatic:用于调用静态方法。

  • invokedynamic:用于在运行时动态解析出调用点限定符所引用的方法,并执行。

举例来说。

public class InvokeExamples {

private void run() {

List ls = new ArrayList();

ls.add(“难顶”);

ArrayList als = new ArrayList();

als.add(“学不动了”);

}

public static void print() {

System.out.println(“invokestatic”);

}

public static void main(String[] args) {

print();

InvokeExamples invoke = new InvokeExamples();

invoke.run();

}

}

我们用 javap -c InvokeExamples.class 来反编译一下。

Compiled from “InvokeExamples.java”

public class com.itwanger.jvm.InvokeExamples {

public com.itwanger.jvm.InvokeExamples();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object.“”😦)V

4: return

private void run();

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 难顶

11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

16: pop

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

20: dup

21: invokespecial #3 // Method java/util/ArrayList.“”😦)V

24: astore_2

25: aload_2

26: ldc #6 // String 学不动了

28: invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z

31: pop

32: return

public static void print();

Code:

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

3: ldc #9 // String invokestatic

5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

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

Code:

0: invokestatic #11 // Method print:()V

3: new #12 // class com/itwanger/jvm/InvokeExamples

6: dup

7: invokespecial #13 // Method “”😦)V

10: astore_1

11: aload_1

12: invokevirtual #14 // Method run:()V

15: return

}

InvokeExamples 类有 4 个方法,包括缺省的构造方法在内。

1)InvokeExamples() 构造方法中

缺省的构造方法内部会调用超类 Object 的初始化构造方法:

invokespecial #1 // Method java/lang/Object."<init>":()V

2)成员方法 run()

invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识

一线互联网P7面试集锦+各种大厂面试集锦

学习笔记以及面试真题解析

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

包括缺省的构造方法在内。

1)InvokeExamples() 构造方法中

缺省的构造方法内部会调用超类 Object 的初始化构造方法:

invokespecial #1 // Method java/lang/Object."<init>":()V

2)成员方法 run()

invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识

[外链图片转存中…(img-QBNjXPKQ-1713707830050)]

一线互联网P7面试集锦+各种大厂面试集锦

[外链图片转存中…(img-IRM4F1yM-1713707830051)]

学习笔记以及面试真题解析

[外链图片转存中…(img-hGLQuScu-1713707830051)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-NXYoyb6y-1713707830052)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值