JVM工作原理与实战(三):字节码文件的组成

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容。

一、基础信息

字节码文件的基础信息包括魔数、字节码文件对应的Java版本号、访问标识(public final等等)、父类和接口内容。

image.gif

1.Magic魔数

文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。软件使用文件的头几个字节(文件头)去校验文件的类型,如果软件不支持该种类型就会出错。在Java字节码文件中,将文件头称为magic魔数

文件类型 字节数 文件头
JPEG (jpg) 3 FFD8FF
PNG (png) 4 89504E47(文件尾也有要求)
bmp 2 424D
XML (xml) 5 3C3F786D6C
AVI (avi) 4 41564920
Java字节码文件 (.class) 4 CAFEBABE

通过NotePad++使用十六进制插件查看class文件:

image.gif

2.主副版本号

版本号用于标识字节码文件的版本。它包括主版本号和副版本号。主版本号表示编译字节码文件的JDK版本,而副版本号则用于标识不同版本的字节码文件。通过比较字节码文件的主版本号和运行时的JDK版本,可以判断两者是否兼容。

JDK1.0-1.1使用了 45.0-45.3,JDK1.2是46之后每升级一个大版本就加1。1.2之后大版本号计算方法为主版本号 - 44,比如主版本号52就是JDK8。

image.gif

3.其他信息

访问标识:在Java字节码文件中用于描述类的访问权限和特性。它包括public、private、protected和默认(无标识)等访问级别,以及final、abstract、interface等修饰符。这些标识确定了类的可见性和行为特性,影响着类的使用和继承。

类、父类、接口索引:在字节码文件中,父类和接口的内容通过索引值来表示。这些索引值指向类或接口在常量池中的位置,以便在运行时能够找到它们的相关信息。通过这些索引值,JVM可以在运行时加载并链接所需的类和接口,从而正确执行程序。

image.gif

二、常量池

常量池是字节码文件中的一个重要组成部分,主要用于存储程序中的常量值,如字符串常量、类或接口名、字段名等。常量池的主要作用是避免相同的内容在字节码文件中重复定义,从而节省空间

在字节码指令中,可以通过常量池中的编号引用相应的常量值。每个常量在常量池中都有一个唯一的编号,编号从1开始。通过这些编号,字节码指令可以快速地定位到常量池中的数据,从而实现高效的数据访问。在字节码指令中引用常量池的过程称为符号引用。通过符号引用来替代实际的数据值,可以在运行时动态地解析和加载相应的数据,提高程序的灵活性和可维护性。

1.案例解析

案例一:

案例代码:

public class ConstantPoolTest {
    public static final String a1 = "This is a test";
    public static final String a2 = "This is a test";
    public static void main(String[] args) {
        ConstantPoolTest constantPoolTest = new ConstantPoolTest();
    }
}

image.gif

查看字段常量值索引(a1和a2),指向cp_info #8:

image.gif


image.gif

进入常量池cp info #8,看到字符串文本内容在cp_info #27:

image.gif

进入常量池cp_info #27,可以看到字符串文本内容:

image.gif

在上面的示例中,字符串常量"This is a test"在常量池中只存储一次。在字节码指令中,可以通过常量池中的编号来引用这两个常量。因此,常量池避免了相同内容的重复定义,节省了存储空间。

案例二:

案例代码:

public class ConstantPoolTest2 {
    public static final String a1 = "abc";
    public static final String a2 = "abc";
    public static final String abc = "abc";
    public static void main(String[] args) {
        ConstantPoolTest2 constantPoolTest = new ConstantPoolTest2();
    }
}

image.gif

查看字段常量值索引,指向cp_info #8:

image.gif

进入常量池cp info #8,看到字符串文本内容在cp_info #10:

image.gif

进入常量池cp_info #10,可以看到字符串文本内容:

image.gif

查看字段abc名称所在常量池,指向cp_info #10:

image.gif

三、方法

1.方法介绍

在JVM字节码文件中,方法部分是核心,它包含了程序执行的具体指令。这些指令是以字节码的形式存在的,是Java源代码的编译结果。

  • 字节码指令:字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。
  • 操作数栈:操作数栈在字节码指令执行中起到关键的作用。它是一个后进先出(LIFO)的数据结构,用于临时存储数据。几乎所有的字节码指令都会用到操作数栈,无论是从栈顶弹出数据,还是将数据压入栈中。
  • 局部变量表:局部变量表是存放方法中定义的局部变量的位置。每个方法在JVM中都有一个关联的局部变量表,这个表定义了方法的参数和在方法体内部声明的局部变量。局部变量表中的每个条目都包含一个变量的名称、类型和其在方法中的偏移量。这个偏移量表示该变量在方法栈帧中的位置。
  • 局部变量表数组:局部变量表数组是存放这些局部变量表的数组。每一个方法对应一个局部变量表,该表记录了该方法的所有局部变量的信息,包括它们的名称、类型和偏移量等。这些信息对于JVM在运行时解析字节码和执行相应的操作非常重要。

右键查看JVM规范:

image.gif

JVM规范:

image.gif

2.案例解析

案例一:

案例代码:

public class Demo1 {
    public static void main(String[] args) {
        int i=0;
        i = i++;
    }
}

image.gif

查看方法的字节码信息:

image.gif

字节码信息解析:

iconst_0 将0放入操作数栈
istore_1 从操作数栈取出放入局部变量表1号位置
iload_1 从局部变量表1号位置加载数据到操作数栈
iinc 1 by 1 在局部变量表1号位置增加1
istore_1 将操作数栈中的值保存到局部变量表
return 方法结束,返回

案例二:

案例代码:

public class Demo2 {
    public static void main(String[] args) {
        int i = 0;
        int j = i + 1;
    }
}

image.gif

查看方法的字节码信息:

image.gif

 字节码信息解析:

iconst_0 将常量0放入操作数栈
istore_1 从操作数栈取出放入局部变量表1号位置
iload_1 将局部变量表1中的数据放入操作数栈
iconst_1 将常量1放入操作数栈
iadd 将操作数栈顶部的两个数据进行累加,结果放入栈中
istore_2 从操作数栈取出放入局部变量表2号位置
return 方法结束,返回

四、字段

在JVM字节码文件中,字段部分包含了当前类或接口声明的字段信息。这些字段包括类的成员变量、静态变量、常量等。字段信息在类的字节码文件中被组织为一个字段表,每个字段在表中都有一个唯一的字段表条目。

image.gif

五、属性

在JVM字节码文件中,属性部分包含了描述类或接口的各种元数据信息。这些属性提供了关于类或接口的附加信息,用于支持某些JVM特性和工具。

常见的属性:

  • 源码的文件名:这个属性提供了类的源文件名,可以帮助调试和错误跟踪。它允许开发人员在运行时与源代码对应,这对于理解和调试代码非常有用。
  • 内部类的列表:如果一个类包含内部类或嵌套类,那么这个属性将列出所有的内部类和嵌套类。这对于解析和执行嵌套类非常关键。
  • 注解:注解是一种提供元数据的方法,可以用于标记代码中的特定元素。在字节码文件中,注解被表示为属性,提供了关于类、方法、字段等的额外信息。
  • 签名:这个属性提供了关于类、方法或字段的签名信息,包括它们的名称和类型。这对于验证类型和调用方法非常重要。
  • 其他属性:还有其他一些属性,如行号表、本地变量表等,这些属性用于支持调试和其他高级功能。

image.gif


总结

JVM是Java程序的运行环境,负责字节码解释、内存管理、安全保障、多线程支持、性能监控和跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容,希望对大家有所帮助。

相关文章
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
28天前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
1月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
48 2
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
28 3
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
1月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
4月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
107 1
|
3月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
6天前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。