5.3.4 方法区

在Java虚拟机中,关于被装载类型的信息存储在一个逻辑上被称为方法区的内存中。当虚拟 机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件--个线性二进制数据流——然后将它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并将这些 信息存储到方法区。该类型中的类(静态)变量同样也是存储在方法区中。

Java虚拟机在内部如何存储类型信息,这是由具体实现的设计者来决定的。比如,在class文 件中,多字节值总是以髙位在前(即代表较大数的字节在前)的顺序存储。但是当这些数据被 引人到方法区后,虚拟机可以以任何方式存储它。假设某个实现是运行在低位优先的处理器上, 那么它很可能会把多字节值以低位优先的顺序存储到方法区。

当虚拟机运行Java程序时,它会査找使用存储在方法区中的类型信息。设计者应当为类型信 息的内部表示设计适当的数据结构,以尽可能在保持虚拟机小巧紧凑的同时加快程序的运行效 率。如果正在设计一个需要在少量内存的限制中操作的实现,设计者可能会决定以牺牲某些运 行速度来换取紧凑性。另外一方面,如果设计一个将在虚拟内存系统中运行的实现,设计者可 能会决定在方法区中保存一些冗余信息,以此来加快执行速度。(如果底层主机没有提供虚拟内 存,但是提供了一个硬盘,设计者可能会在实现中创建一个虚拟内存系统。)java虚拟机的设计 者可以根据目标平台的资源限制和需求,在空间和时间上做出权衡,选择实现什么样的数据结 构和数据组织。

在Java class文件和虚拟机中,类型名总是以全限定名出现。在Java源代码中,全限定名由 类所属包的名称加一个再加上类名组成。例如,类Object的所属包为java.lang,那它的全 限定名应该是java.lang.Object,但在class文件里,所有的都被斜杠“/“代替,这样就成为 java/lang/Object,至于全限定名在方法区中的表示,则因不同的设计者有不同的选择而不同可 以用任何形式和数据结构来代表。
除了上面列出的基本类型信息外,虚拟机还得为每个被装载的类型存储以下信息:
•该类型的常量池。
•字段信息。
•方法信息。
•除了常量以外的所有类(静态)变量。
•—个到类classLoader的引用。
•一个到Class类的引用。

常量池虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用常量的一 个有序集合,包括直接常量(string、integer和floating point常量)和对其他类型、字段和方法 的符号引用。池中的数据项就像数组一样是通过索引访问的。因为常量池存储了相应类型所用 到的所有类型、字段和方法的符号引用,所以它在Java程序的动态连接中起着核心的作用。

类(静态)变量类变量是由所有类实例共享的,但是即使没有任何类实例,它也可以被访 问。这些变量只与类有关——而非类的实例,因此它们总是作为类型信息的一部分而存储在方 法区。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须在方法区中为这些类 变量分配空间。

而编译时常量(就是那些用final声明以及用编译时已知的值初始化的类变量)则和—般的类 变量的处理方式不同,每个使用编译时常量的类型都会复制它的所有常量到自己的常量池中 或嵌人到它的字节码流中。作为常量池或字节码流的一部分,编译时常量保存在方法区中就和一般的类变量一样:但是当一般的类变量作为声明它们的类型的一部分数据而保存的时候 编译时常量作为使用它们的类型的_部分而保存。这种特殊处理方式在第6章中更详细地讨论。


指向ClassLoader类的引用每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器 还是由用户自定义类装载器装载的。如果是用户自定义类装载器装载的,那么虚拟机必须在类 型信息中存储对该装载器的引用。这是作为方法表中的类型数据的一部分保存的。

虚拟机会在动态连接期间使用这个信息。当某个类型引用另一个类型的时候,虚拟机会请 求装载发起引用类型的类装载器来装载玻引用的类型。这个动态连接的过程,对于虚拟机分离 命名空间的方式也是至关重要的。为了能够正确地执行动态连接以及维护多个命名空间,虚拟 机需要在方法表中得知每个类都是由哪个类装载器装载的。关于动态连接和命名空间的细节请 参见第8章。

指向Class类的引用对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为 它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区 中的类型数据关联起来。

所有这些信息都直接从方法区中获得。

虚拟机开始执行main()方法,在执行时,它会一直持有当前类(Volcano类)的常量池(方法区中的一个数据结构)的指针。

main ()的第一条指令告知虚拟机为列在常量池第一项的类分配足够的内存。所以虚拟机 使用指向Volcano常量池的指针找到第一项,发现它是一个对Lava类的符号引用,然后它就检査 方法区,看Lava类是否已经被装载了。

这个符号引用仅仅是一个给出了类Lava的全限定名“Lava”的字符串。为了能让虚拟机尽 可能快地从一个名称找到类,设计者应当选择最佳的数据结构和算法。这里可以采用各种方法, 如散列表,搜索树等等。同样的算法也可以用于实现Class类的forName ()方法,这个方法根 据给定的全限定名返回Class引用。

当虚拟机发现还没有装载过名为“Lava”的类时,它就开始查找并装载文件“LavaxUss”, 并把从读人的二进制数据中提取的类型信息放在方法区中。

紧接着,虚拟机以一个直接指向方法区Lava类数据的指针来替换常量池第一项(就是那个 字符串“Lava”)——以后就可以用这个指针来快速地访问Lava类了。这个替换过程称为常量池 解析,即把常量池中的符号引用替换为直接引用。这是通过在方法区中搜索被引用的元素实现的,在这期间可能又需要装载其他类。在这里,我们替换掉符号引用的“直接引用”是一个本地指针。

终于,虚拟机准备为一个新的Lava对象分配内存。此时,它又需要方法区中的信息。还记 得刚刚放到Volcano类常量池第一项的指针吗?现在虚拟机用它来访问Lava类型信息(此前刚放 到方法区中的),找出其中记录的这祥一个信息:一个Lava对象需要分配多少堆空间。

Java虚拟机总能够通过存储于方法区的类型信息来确定一个对象需要多少内存,但是,某个 特定对象事实上需要多少内存,是跟特定实现相关的。对象在虚拟机内部的表示是由实现的设 计者来决定的,本章稍后将详细讨论这个问题。

当Java虚拟机确定了一个Lava对象的大小后,它就在堆上分配这么大的空间,并把这个对象 实例的变量speed初始化为默认初始值0。假如Lava类的超类Object也有实例变量,则也会在此时 被初始化为相应的默认值。更多信息请参考第7章。

当把新生成的Lava对象的引用压到栈中,main ()方法的第一条指令也完成了。接下来的 指令通过这个引用调用Java代码(该代码把speed变量初始化为正确初始值5 )。另外一条指令将 用这个引用调用Lava对象引用的fl0W ()方法。

原文地址:https://www.cnblogs.com/mongotea/p/11979313.html