从字节码文件聊到 i=i++

在使用字节码文件分析 i = i++ 以前,咱们先来看一些必要的前置知识,若是你已经懂了,能够直接略过小程序

一个简单概念

之前咱们常常说,Java 是跨平台的语言,可是 Java 为何跨平台呢?实际上是 JVM 的功劳,JVM实际上是一种规范,HotSpot、J九、Taobao VM、Zing 等等都是它的具体实现。同时,咱们也能够将 Java 虚拟机理解为执行在 OS 的一个软件,理论上也是酱紫的。Java 文件被编译成为 class 文件,而后这个 class 文件由 JVM 来进行解析执行this

明白了这一点以后再来分析,既然 JVM 才是跨平台的关键,而 JVM 是用来解析执行 class 文件的,那么就能够推测出全部能编译为 class 文件的语言都是能够在 JVM 上解析执行,作到跨平台spa

实际上也是如此的,目前已经有巨多的语言作到了,不再局限于 Java!好比 scala、kotlin、groovy、clojure、jython(我没有写错)等等插件

从跨平台的语言到跨语言的平台,你了解了么线程

字节码文件的结构

没有比下面这个更简单的 Java 小程序了scala

package com.bl.classloader;

/**
* @Author BarryLee
*/
public class ByteCode1 {
}
code

咱们尝试着使用 sublime 打开它生成的字节码文件,下面是前面的三行的十六进制对象

cafe babe 0000 0034 0010 0a00 0300 0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69blog

个人天,这是人看的嘛。。莫急,来看看介个(来自马老师的图image.png接口

而后再一个个看,能和上边对应起来的,有兴趣能够看看 JVMS 这个官方文档

magic 咱们通常把他叫作魔数,其实这四字节它是标识着这个文件是什么文件,比方说这里就是 CAFE BABE,就代表了人家是一个 class 文件,想一想看,正好跟 Java 的 logo 相呼应,很好记

minor 小版本号

major version 大版本号,这里十六进制的 34 也就是对应着十进制的 52,也就是 Java8

constant pool_count 常量池里有多少个常量

constant pool 常量池里都有些啥,划重点,等等要烤的

access flags 这个类的标识信息,好比 private、default、protected、public,是 class 仍是 interface

this_class 当前类

super_class 还要讲嘛,父类

interface_count 接口的数量

interfaces 具体的接口

filds_count 有多少个字段

fields 有哪些字段,也就是属性啦

methods_count 方法有多少个

methods 方法都有些啥

attribute_count 参数个数

attribute 参数,里面最最重要的就是 code -- 代码

一个很棒的 IDEA 插件

十六进制数确实不是人看的,因此,在你的 IDEA 装个插件吧

直接在插件商店搜 jclasslib 就能找到它了,下它,使用很简单,代码里点一下你想看的类,而后 view -> shwo bytecode with jclasslib 就能够啦

打开前面写的 ByteCode1
批注 2020-01-08 110143.png

是否是很棒,插件已经帮咱们分析好了

内存加载大体的过程

  1. Loading 类加载器将字节码文件从硬盘加载到虚拟机内存
  2. Linking 包括如下三个步骤

    verification 校验字节码文件是否合法,magic 是否为 cafe babe

    preparation 静态变量赋默认值(注意不是你在代码里给定的初始值)

    resolution 将符号引用解析为直接引用

  3. Initializing 静态变量赋初始值

JVM 内存模型简单过一遍

最后一步了,很重要哦。

Program Counter 程序计数器,简称 PC,用来放指令的位置,也就是说能够用它来找到下一条待执行的指令

JVM Stacks 也就是 Java 虚拟机栈,内容有点多仍是放到下面讲

Native Method Stacks 本机方法栈,也就是 C++ 这种底层的东西了,咱们管不着

Heap 堆,对象的存放位置,因此也就是GC 的重点

Method Area 方法区,方法区其实只是一个概念,在 1.8 及以后的实现叫作 meta space

Direct Memory 直接内存

聊聊 JVM Stack

每一个线程都会有一个 JVM Stack,每一个 JVM Stack 都会有不少的栈帧 - Stack Frame,每一个方法都对应着一个个的 Stack Frame 压在 JVM Stack 里头

每一个 Frame 又有四个内容,是等等分析 i = i++ 的重点要掌握的,嗯,看个人 xmind 吧,懒得写了

image-20200108111745153.png


开始分析 i = i++

来看这段小程序,若是时 j = i++,也就是先赋值后加加,相信你必定知道 j 确定是 8,但如今是 i = i++,输出结果倒是 8,下面咱们经过字节码的层面来分析这个小程序(打开你的 jclasslib)

public static void main(String[] args) {    
int i = 8;
i = i++;
// i = ++i;
System.out.println(i);
}

下面这是所谓的 JVM 指令,学过汇编的应该能看懂一些简单的指令,不单独讲了,想了解更多,直接点它,jclasslib 帮你直接跳转到官网,看看官网,就好了
image-20200108115137242.png

下面这是前边讲的局部变量表
image-20200108094956836.png

咱们来一个个指令的分析

bipush 8 将 8 压栈(操做数栈)

istore_1,首先将 8 弹栈,而后放到局部变量表,等同于赋值,将 8 给了 i

iload_1, 再把 i 的值 8 又压到栈里

iinc 1 by 1 局部变量表 No.为 1 对应的数 +1,变成了 9

istore_1 而后又把栈里的 8 弹出赋值给了局部表量表的 1 位置,也就是覆盖了

所以,i=i++ 最后为8,下面的指令就是打印以及 return 了

再看看 i = ++i

image-20200108115544672.png

不知道你看出来区别了没有,i = i++ 是先把 8 压栈,后面又弹出来覆盖了局部变量表

而这里是先iinc 1 by 1了,而后把 9 压栈,在从新赋值给 i,额,实际上是多余的一次赋值,写成 ++i 就得了

仍是简单解读如下这些指令8^_^

bipush -> b + i + push -> i很小byte能装下 + 变量i + 压栈

istore -> i + store -> 弹栈而后赋值给局部变量表的i

iinc 1 by 1 -> i + inc + 1 + by 1-> 局部变量表为1的位置加一

getstatic 我也没看过

invokevirtual 这个比较复杂,是 invoke指令的一种,学过反射的同窗都应该知道 invoke 就是执行方法

  1. InvokeStatic 执行静态方法
  2. InvokeVirtual 是自带多态的一个指令,好比 List l = new ArrayList(),调用l.add()就是这个virtual了
  3. InvokeInterface 接口,
  4. InovkeSpecial 能够直接定位,不须要多态的方法 private 方法,构造方法
  5. InvokeDynamic JVM最难的指令 lambda表达式或者反射或者其余动态语言scala kotlin,或者CGLib ASM,动态产生的class,会用到的指令

嗯,没了,相信到这里你已经完全懂了 i = i++ 这个无赖的梗了

相关文章
相关标签/搜索