博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaClass文件结构你知道多少?
阅读量:7021 次
发布时间:2019-06-28

本文共 4340 字,大约阅读时间需要 14 分钟。

JavaClass文件结构

[TOC]

1. 前言

​ 这边文章是基于读者对Java,编译原理,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: ()Vflags: ACC_PUBLICCode: 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弹出并赋值给变量a2: iconst_1				:将int1压入栈顶3: istore_2				:将栈顶的1弹出并赋值给变量b4: iconst_1				:将int1压入栈顶5: istore_3				:将栈顶的1弹出并赋值给变量c6: iconst_2				:将int2压入栈顶7: istore        4		:将栈顶的2弹出并赋值给本地变量d9: getstatic     #2		:获取PrintStream对象12: new           #3    :创建StringBuilder对象,并将对象的引用值压入栈顶,stack=115: dup					:复制栈顶的值,并且压入栈顶,stack=216: invokespecial #4    :执行StringBuilder的初始化方法           19: iload_1				:将变量a的值压入栈顶,stack=320: invokevirtual #5    :执行append方法,依次弹出栈顶的a和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2              23: ldc           #6    :将字符s压入栈顶,stack=325: invokevirtual #7    :执行append方法,依次弹出栈顶的s和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2。28: iload_2				:将变量b的值压入栈顶,stack=329: invokevirtual #5    :执行append方法,依次弹出栈顶的b和StringBuilder,stack=1,执行完成后将StringBuilder对象的引用值压入栈顶,stack=2                32: iload_3				:同2833: invokevirtual #5    :同29     36: iload         4		:同2838: invokevirtual #5    :同29    41: invokevirtual #8    :执行StringBuilder的toString方法,并将返回的字符串压入栈顶,stack=2    44: invokevirtual #9    :将栈顶的字符串弹出,执行println方法,stack=147: 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

你可能感兴趣的文章
Java面试记录20151218
查看>>
原始模型模式
查看>>
PostgreSQL一键安装包在XP系统中的中文化问题
查看>>
ant编译打包可运行的jar文件,并包含所依赖的第三方jar包
查看>>
从 Git@OSC 被攻击来看互联网的恶劣环境
查看>>
WEB性能测试工具推荐
查看>>
NAT基本原理
查看>>
《工具 系列》 - 目录
查看>>
【手把手教你全文检索】Apache Lucene初探
查看>>
sql2000 sp_password 错误
查看>>
单页面应用简介
查看>>
关联关系映射
查看>>
centos7 快速卸载openjdk
查看>>
排序代码练习
查看>>
Java泛型
查看>>
ie8,ff,google,opera都不乱,国产(360和傲游)乱
查看>>
工厂模式
查看>>
spring-boot项目在外部tomcat环境下部署
查看>>
正在创业或准备创业的你如何组建技术团队?
查看>>
什么是句柄?为什么会有句柄?HANDLE
查看>>