JavaClass文件结构你知道多少?

JavaClass文件结构

[TOC]

1. 前言

​ 这边文章是基于读者对Java,编译原理,jvm规范有一定了解后书写的。对上述知识缺乏了解的可以执行参考

jvm规范第四章,该章节对class文件结构,jvm字节码质量有详细描述。

​ 本文中主要使用到的字节码指令有 :

  1. -dup

  2. iadd

  3. iconst_1, iconst_2,iconst_3

  4. istore,istore_1,istore_2,istore_3

  5. iload,iload_1,iload_2,iload_3

  6. new

  7. getstatic

  8. dup

  9. invokespecial

  10. invokevirtual

  11. retun

    本文使用的java命令有:

  12. javac

  13. javap

2. JVM解释运行过程

​ java语言的运行,是将java文件编译成class文件,然后加载到虚拟机上运行的。在不考虑JIT的情况下,jvm是解释执行的。而且是基于栈实现的运算。这句话是什么意思呢?比如我们常见的一行代码运算

int a = 2+ 3;

​ 这是一个常规的赋值表达式,含义是声明一个变量a,将2+3发的值赋值给变量a。其中2+3是生活中常见的数学表达式。我们称这种表达式为中缀表达式。而经过javac编译之后将形成后缀表达式的形式:2 3 + 的形式。java的解释执行器在读入该行代码的时候,会将2从内存加载到栈里面;然后读取3加载到栈顶,然后读取到+运算符,就会将+运算符需要的两个参数,就是存放在栈里面的2和3弹出来,然后送给CPU做加法运算。CPU完成运算之后,将运算结果5压入栈中。然后通过istore命令将5的值存到变量a指向的内存地址中。

3. class文件结构说明

​ 我们来看一下源文件Test.java

public class Test{
public void concat(){
   int a = 1;
   int b = 1;
   int c = 1;
   int d = 2;
   System.out.println(a + "s" + b + c + d);
}
}
复制代码

​ 我们通过javac Test.java 命令编译,然后再使用javap -verbose Test命令查看变异后的class文件结构,我们截取一部分主要内容如下:

public void concat();
descriptor: ()V
flags: ACC_PUBLIC
Code:
 stack=3, locals=5, args_size=1
    0: iconst_1
    1: istore_1
    2: iconst_1
    3: istore_2
    4: iconst_1
    5: istore_3
    6: iconst_2
    7: istore        4
    9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
   12: new           #3                  // class java/lang/StringBuilder
   15: dup
   16: invokespecial #4                  // Method java/lang/StringBuilder."":()V
   19: iload_1
   20: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   23: ldc           #6                  // String s
   25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   28: iload_2
   29: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   32: iload_3
   33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   36: iload         4
   38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   41: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   44: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   47: return
 LineNumberTable:
   line 12: 0
   line 13: 9
   line 14: 47

复制代码

上面的源码只截取的concat方法的的编译结果,我们重点关注下Code部分。

3.1 stack

​ 我们首先关注的是stack=3,这句话的含义是concat方法在执行的过程中需要最大的操作栈深度为3。根据我们上一节的说明的,jvm是基于栈进行解释执行的。我们按照编译出来的jvm字节码一行一行的模拟jvm运行过程。

0: iconst_1				:将int1压入栈顶
1: istore_1				:将栈顶的1弹出并赋值给变量a
2: iconst_1				:将int1压入栈顶
3: istore_2				:将栈顶的1弹出并赋值给变量b
4: iconst_1				:将int1压入栈顶
5: istore_3				:将栈顶的1弹出并赋值给变量c
6: iconst_2				:将int2压入栈顶
7: istore        4		:将栈顶的2弹出并赋值给本地变量d
9: getstatic     #2		:获取PrintStream对象
12: new           #3    :创建StringBuilder对象,并将对象的引用值压入栈顶,stack=1
15: dup					:复制栈顶的值,并且压入栈顶,stack=2
16: invokespecial #4    :执行StringBuilder的初始化方法           
19: iload_1				:将变量a的值压入栈顶,stack=3
20: invokevirtual #5    :执行append方法,依次弹出栈顶的a和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2              
23: ldc           #6    :将字符s压入栈顶,stack=3
25: invokevirtual #7    :执行append方法,依次弹出栈顶的s和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2。
28: iload_2				:将变量b的值压入栈顶,stack=3
29: invokevirtual #5    :执行append方法,依次弹出栈顶的b和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2                
32: iload_3				:同28
33: invokevirtual #5    :同29     
36: iload         4		:同28
38: invokevirtual #5    :同29    
41: invokevirtual #8    :执行StringBuilder的toString方法,并将返回的字符串压入栈顶,stack=2    
44: invokevirtual #9    :将栈顶的字符串弹出,执行println方法,stack=1
47: return				:当前方法返回,void无返回值。
复制代码

观察整个执行过程后,可以得出,test在执行的过程中,使用到的最大操作数栈的深度为3。

3.2 locals

​ locals是本地变量的数量。本例中,共需要存储三个1,一个2和一个this指针。所以本地变量表中需要5个slot存储变量,当然我们也要注意long和double型变量,这两个都是64位的,所以需要两个slot去存储。但本例中,5个变量都是32位的,所以不需要扩展slot。

3.3 arg_size

​ arg_size是方法参数的个数,因为该方法是实例方法,所以会默认传入this指针作为参数,所以就需要占用一个本地变量表的位置和一个参数位。如果将concat方法改为static的,则不需要传入this指针,就不会去占用了。此时locals将变成4,arg_size则变为0。读者可自行验证。

4. 结论

  • -javac编译的时候,会把我们常见的中缀表达式翻译成jvm使用的后缀表达式。
  • jvm是基于栈进行解释执行的。
  • class文件中的statck是方法执行过程中调用的最大操作数栈深度。
  • class文件中的locals是本地变量表中的变量需要的slot的个数。
  • clsss文件中的arg_size是方法的参数个数,static方法不需要传入this指针,单非static防范默认会传入this指针。
  • this指针要占用本地变量表中的slot个数和方法参数的个数。

转载于:https://juejin.im/post/5ce61ac8e51d4556bb4cd2ec

你可能感兴趣的:(JavaClass文件结构你知道多少?)