Java虚拟机的加载子系统在加载一个类型(类或接口)的时候,主要完成以下三件事:
- 由一个类型的全限定名查找对应的二进制流(可能class文件,也可能是数据库中的二进制或来自网络的字节流)
- 根据二进制流转为虚拟机方法区中的运行时数据结构。
- 在Java堆中生成代表该类型的java.lang.Class对象,作为方法区类型数据的访问入口。
接下来就详细说说方法区中的运行时数据结构具体包括哪一些。在虚拟机规范中,对于方法区的规定十分抽象,因此跟具体的实现有很大的关系。但是一般都具有一些共同的部分。由于虚拟机的方法区使线程共享的,因此访问这些数据必须是线程安全的。根据《深入Java虚拟机》(Bill Venners)的描述,方法区中的数据包括以下内容:
类型信息(Type information)
- 类型的全限定名(class文件中的this_class)
- 类型的直接超类的全限定名(class文件中的super_class),Object类型除外。
- 直接超接口的全限定名列表(对应interfaces表集合)
- 该类型是类类型还是接口类型。
- 类型的访问修饰符(如public、abstract、final等,对应access_flags)
其中的全限定名把Java源码中的“ . ”换成“ / ”
类型常量池
常量池中存放的是该类型所用到的常量的有序集合,包括直接常量(CONSTANT_String、CONSTANT_Integer等)和对其他类型、字段和方法的符号引用(symbolic reference),这个常量池像数组一样通过索引来访问,在动态连接中起着核心作用。如果用javap -verbose工具,可以得到虚拟机指令的“汇编程序”,可以看到指令中会用#后跟索引来表示对常量池的访问。例如getstatic #9。
字段信息
注意区别常量池中的字段引用与这里的字段信息。字段信息,是指该类中声明的所有字段(包括类级变量和实例变量,不包括局部变量)的描述,如字段名称、字段修饰符等。
具体如下:
- 字段名
- 字段的类型(可能是基本类型或引用类型)
- 字段的修饰符(public、static、transient等)
这些信息可以从class字节流中的fields_info中找到。注意的是,字段的顺序也需要保留,当然还可能包含其他一些信息。
方法信息
和字段很类似的,包括:
- 方法名
- 方法返回类型
- 方法参数的个数和类型、顺序等
- 方法的修饰符。
方法的顺序也是需要保存的,除此以外,如果方法不是抽象或本地的,还必须保存:
- 方法的字节码(bytecodes)
- 操作数栈和该方法在栈帧中的局部变量区的大小等。
- 异常表
这些信息可以造class字节流的method_ref中找到。
类变量
由于类变量时所有对象共享的,因此并不保存在堆栈中,而是保存在方法区中。即使没有任何实例对象,也可以访问这些类变量。这些类变量只与类挂钩。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须为这些类变量分配内存空间(在连接过程的准备阶段,会为类变量分配内存并设置为默认值(而不是初值))。对于编译时常量,则直接将其复制到使用它们(编译时常量)的类的常量池当中,或者作为字节码流的一部分。比如,类C声明了一个final static(即编译时常量)的字段field1和static的field2,在D类和E类中用到了field1,则在类D和类E的常量池中,都会保存有field1的副本。而field2则保存在类C的方法区当中。【英文原文:non-final
class variables are stored as part of the data for the type that declares them, final class variables are stored as part of the data for any type thatuses them. 】
指向类加载器的引用:(A Reference to Class ClassLoader)
一个类可以被启动类加载器或者自定义的类加载器加载,如果一个类被某个自定义类加载器的对象(实例)加载,则方法区中必须保存对该对象的引用。例如,我们自己定义了一个类加载器GreeterClassLoader,并实例化一个实例myLoader,然后用myLoader加载类类C,则在C的方法区中,必须保存对myLoader的引用。这样,当类C引用了类D并且类D还未被加载时,虚拟机就会请求myLoader对象来加载类型D。
指向Class实例的引用
在加载过程中,虚拟机会创建一个代表该类型的Class对象,方法区中必须保存对该对象的引用。可以通过Class类的forName静态方法来得到该Class对象。例如Class.forName("java.lang.Thread")将会返回一个代表Thread类的Class对象。但是如果虚拟机无法将Class类加载到当前的命名空间,则会抛出ClassNotFoundException。另以后总方式可以得到Class对象的引用,例如我有一个Object的对象实例obj,则直接调用obj.getClass()就可以得到一个代表Object类的Class对象的引用。通过该引用,就可以获取一些类型信息,例如getName、getSuperClass等。
方法表
方法表是为了提高访问效率的,并不一定包含这一项。虚拟机可能会对每个装载的非抽象类,都声称一个方法表,作为一部分类型信息保存在方法区中。方法表示一个数组,每个数组元素是实例可能调用的方法的直接引用(指向方法信息),包括从超类继承来的方法。而对于抽象类和接口,保存方法表则毫无用处,因为不可能实例化一个抽象类或者接口。运行的时候就可以通过方法表快速搜寻对象调用的方法(常量池中的方法引用在解析的时候被解析成方法表索引??)。
区别字段引用和字段信息、方法引用与方法信息很重要。
下面是一个具体的实例,来说明方法区中的信息:
class Lava {
private int speed = 5;
void flow() {
System.out.println("I am flowing 5 kilometers per hour");
}
}
public class Volcano {
public static void main(String[] args) {
Lava lava = new Lava();
lava.flow();
}
}
首先必须告诉虚拟机初始类的名字是Volcano,例如你在命令行输入:java Volcano。虚拟机找到并读入想要的class文件Volcano.class。然从这个二进制流中提取类型信息并放到方法区中,经过一些诸如验证、准备等过程后,虚拟机开始执行main()方法,运行时,虚拟机持有指向当前类的常量池的指针。需要注意的是,开始运行main()方法的字节码时,
Lava类还没有装载。虚拟机至于在需要的时候才装载相应的类。main方法虚拟机指令如下:
第一条“汇编指令” new #16 告诉虚拟机为常量池的第16项分配好足够的内存,所以虚拟机通过指向常量池的指针找到第16项,发现它是一个对Lava类的符号引用,接着检查方法区,看Lava是否已经被装载。发现哈没有装载后,它就开始查找Lava.class的文件,并且读入到方法区中。读入之后,就将16号符号引用替换为直接引用(本地指针、地址),以后就可以通过这个指针快速访问Lava类。这个过程也被称为常量池解析。
现在,虚拟机准备为一个Lava对象分配内存了,通过第16项的直接引用,可以访问Lava类型信息,然后计算出一个Lava独享需要多少内存。OK,将new指令生成的lava对象引用压入栈顶。然后通过这个引用调用invokespecial #18,这是对象的初始化方法<init>,将speed赋值为5. astore_1指令将lava对象引用存入到局部变量表的1号slot。 aload_1将引用压入栈顶,然后执行 invokevirtual
#19,即调用lava对象的flow方法。最后返回,结束main方法。
方法区使很重要的内存区域,需要在以后不断总结。
分享到:
相关推荐
深入讲解Java虚拟机系列之方法区 在 Java 虚拟机中,方法区是一块非常重要的区域,它存储了所有类的信息,包括类名、父类、接口、权限修饰符、常量池、变量信息、方法信息、静态变量等。方法区是 Java 虚拟机中的一...
Java 虚拟机的内存结构包括方法区(method area)和堆(heap)。方法区保存了从类文件中解析出来的信息。堆保存了程序执行时创建的对象。每一个线程都有自己的 PC 寄存器(程序计数器)和 Java 堆栈(Java stack)。...
第2章概述Java虚拟机的整体架构,包括class文件格式、数据类型、原始类型、引用类型、运行时数据区、栈帧、浮点算法、异常等,这对理解本书后面的内容有重要帮助;第3章详述如何将Java语言编写的程序转换为Java...
Java虚拟机规范 Java SE 8版-带目录-pdf,本书完整而准确地阐释了Java虚拟机各方面的细节,围绕Java虚拟机整体架构、编译器、class文件格式、加载、链接与初始化、指令集等核心主题对Java虚拟机进行全面而深入的分析...
第1章 :简单地介绍了Java虚拟机的历史并吹捧了←_← 一下Java的平台无关性(一次编译,到处运行); 第2章:概览Java虚拟机整体架构; 第3章:介绍如何将Java语言编写的程序转换为虚拟机指令集; 第4章:定义...
Java虚拟机指令集是虚拟机执行的基本操作单元,每条指令对应一个特定的操作,如加载和存储变量、算术运算、控制流程、对象创建和方法调用等。这些指令是无操作数的,它们的参数通常在操作数栈上找到。Java SE 7版的...
本书摒弃了传统的以解读枯燥的Java虚拟机规范文档和分析繁琐的Java虚拟机源代码的方式来讲解Java虚拟机,取而代之的是,以实践的方式,引导读者如何从零开始构建和实现一个Java虚拟机,整个过程不仅能让读者做到对...
Java虚拟机(JVM)是Java程序运行的基础,它负责执行Java字节码,提供了一个与平台无关的执行环境。JVM规范定义了JVM的结构、指令集和运行时数据区,以及如何执行指令和处理异常。自1999年以来,JVM规范经历了多次...
第二章:java虚拟结构(运行时区域内存:寄存器,java虚拟机栈,java堆,方法去,运行时常量池,本地方法栈); 第三章:为java虚拟机编译; 第四章:Class文件格式; 第五章:加载、链接与初始化
Java虚拟机(JVM)是实现Java技术的关键组件,它为Java程序提供了一个运行环境。Java程序在编写后会被编译成一种称为字节码的中间表示形式,这种字节码可以跨平台运行,因为JVM负责将字节码转换成机器代码。JVM的...
总的来说,本地方法栈和执行引擎是Java虚拟机实现复杂功能的关键组件。本地方法栈使Java能够调用本地库,执行系统级别的操作,而执行引擎则确保字节码的有效执行,优化程序性能。两者共同协作,为Java程序提供了一个...
Java虚拟机的内部结构包括类装载器、运行时数据区、执行引擎、本地方法接口和本地库。其中,类装载器负责加载类文件,运行时数据区存储线程的工作数据,执行引擎解析并执行字节码,本地方法接口允许JVM调用非Java...
Java虚拟机(JVM,Java Virtual Machine)是Java平台的核心组成部分,它负责执行Java程序,为Java代码提供了跨平台的运行环境。Java虚拟机的概念始于Sun Microsystems,现在由Oracle公司继续发展和维护。JVM的设计...
第2章概述Java虚拟机的整体架构,包括class文件格式、数据类型、原始类型、引用类型、运行时数据区、栈帧、浮点算法、异常等,这对理解本书后面的内容有重要帮助;第3章详述如何将Java语言编写的程序转换为Java...
### Java虚拟机规范(JVM)概览 #### 核心概念与重要性 《Java虚拟机规范(JavaSE7版)》是理解Java虚拟机(JVM)运作机制的基石,由Tim Lindholm、Frank Yellin、Gilad Bracha和Alex Buckley等人撰写,后由周志明、...
Java虚拟机(JVM)是Java编程语言的核心组成部分,它为Java程序提供了跨平台的运行环境。Java程序在编写完成后,会被编译成字节码(.class文件),这些字节码可以在任何装有JVM的系统上运行,实现了“一次编写,到处...
本书共分20章,第1-4章解释了java虚拟机的体系结构,包括java栈、堆、方法区、执行引擎等;第5-20章深入描述了java技术的内部细节,包括垃圾收集、java安全模型、java的连接模型和动态扩展机制、class文件、运算及...
随着越来越多的第三方语言(Groovy、Scala、JRuby等)在Java虚拟机上运行,Java...《实战Java虚拟机——JVM故障诊断与性能优化》将通过200余示例详细介绍Java虚拟机中的各种参数配置、故障排查、性能监控以及性能优化。