InsideJVM(3)--Method area(方法区)[1]

[入库:2005年8月19日] [更新:2007年3月25日]

本文简介:选择自 tridust 的 blog

 

方法区
在一个jvm实例的内部,类型信息被存储在一个称为方法区的内存
逻辑区中。类型信息是由类加载器在类加载时从类文件中提取出来
的。类(静态)变量也存储在方法区中。

jvm实现的设计者决定了类型信息的内部表现形式。如,多字节变量
在类文件是以big-endian存储的,但在加载到方法区后,其存放
形式由jvm根据不同的平台来具体定义。

jvm在运行应用时要大量使用存储在方法区中的类型信息。在类型信息
的表示上,设计者除了要尽可能提高应用的运行效率外,还要考虑空间
问题。根据不同的需求,jvm的实现者可以在时间和空间上追求一种平
衡。

因为方法区是被所有线程共享的,所以必须考虑数据的线程安全。假如
两个线程都在试图找lava的类,在lava类还没有被加载的情况下,只
应该有一个线程去加载,而另一个线程等待。

方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样
方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中
分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺
寸。

方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态
扩展java程序,一些类也会成为垃圾。jvm可以回收一个未被引用
类所占的空间,以使方法区的空间最小。

类型信息
对每个加载的类型,jvm必须在方法区中存储以下类型信息:
一 这个类型的完整有效名
二 这个类型直接父类的完整有效名(除非这个类型是interface或是
    java.lang.object,两种情况下都没有父类)
三 这个类型的修饰符(public,abstract, final的某个子集)
四 这个类型直接接口的一个有序列表

类型名称在java类文件和jvm中都以完整有效名出现。在java
源代码中,完整有效名由类的所属包名称加一个".",再加上类名
组成。例如,类object的所属包为java.lang,那它的完整
名称为java.lang.object,但在类文件里,所有的"."都被
斜杠“/”代替,就成为java/lang/object。完整有效名在
方法区中的表示根据不同的实现而不同。

除了以上的基本信息外,jvm还要为每个类型保存以下信息:
 类型的常量池( constant pool)
 域(field)信息
 方法(method)信息
 除了常量外的所有静态(static)变量

常量池
jvm为每个已加载的类型都维护一个常量池。常量池就是这个
类型用到的常量的一个有序集合,包括实际的常量(string,
integer, 和floating point常量)和对类型,域和方法
的符号引用。池中的数据项象数组项一样,是通过索引访问的。
因为常量池存储了一个类型所使用到的所有类型,域和方法的
符号引用,所以它在java程序的动态链接中起了核心的作用。

域信息
jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序,
域的相关信息包括:
域名
域类型
域修饰符(public, private, protected,static,final
        volatile, transient的某个子集)
       
方法信息
jvm必须保存所有方法的以下信息,同样域信息一样包括声明顺序
方法名
方法的返回类型(或 void)
方法参数的数量和类型(有序的)
方法的修饰符(public, private, protected, static, final,
           synchronized, native, abstract的一个子集)
除了abstract和native方法外,其他方法还有保存
方法的字节码(bytecodes)
操作数栈和方法栈帧的局部变量区的大小           
异常表

类变量(
  class variables
  译者:就是类的静态变量,它只与类相关,所以称为类变量
)
类变量被类的所有实例共享,即使没有类实例时你也可以访问它。
这些变量只与类相关,所以在方法区中,它们成为类数据在逻辑上
的一部分。在jvm使用一个类之前,它必须在方法区中为每个
non-final类变量分配空间。

常量(被声明为final的类变量)的处理方法则不同,每个常量都
会在常量池中有一个拷贝。non-final类变量被存储在声明它的
类信息内,而final类被存储在所有使用它的类信息内。

对类加载器的引用
jvm必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。
如果一个类型是由用户类加载器加载的,那么jvm会将这个类加载器的
一个引用作为类型信息的一部分保存在方法区中。

jvm在动态链接的时候需要这个信息。当解析一个类型到另一个类型的引用
的时候,jvm需要保证这两个类型的类加载器是相同的。这对jvm区分
名字空间的方式是至关重要的。

对class类的引用
jvm为每个加载的类型(译者:包括类和接口)都创建一个
java.lang.class的实例。而jvm必须以某种方式把
class的这个实例和存储在方法区中的类型数据联系起来。

你可以通过class类的一个静态方法得到这个实例的引用
// a method declared in class java.lang.class:
public static class forname(string classname);

假如你调用forname("java.lang.object"),你会得到与
java.lang.object对应的类对象。你甚至可以通过这个函数
得到任何包中的任何已加载的类引用,只要这个类能够被加载
到当前的名字空间。如果jvm不能把类加载到当前名字空间,
forname就会抛出classnotfoundexception。
(译者:熟悉com的朋友一定会想到,在com中也有一个称为
      类对象(class object)的东东,这个类对象主要
      是实现一种工厂模式,而java由于有了jvm这个中间
      层,类对象可以很方便的提供更多的信息。这两种类对象
      都是singleton的)

也可以通过任一对象的getclass()函数得到类对象的引用,
getclass被声明在object类中:
// a method declared in class java.lang.object:
public final class getclass();
例如,假如你有一个java.lang.integer的对象引用,可以
激活getclass()得到对应的类引用。

通过类对象的引用,你可以在运行中获得相应类存储在方法区
中的类型信息,下面是一些class类提供的方法:
// some of the methods declared in class java.lang.class:
public string getname();
public class getsuperclass();
public boolean isinterface();
public class[] getinterfaces();
public classloader getclassloader();

这些方法仅能返回已加载类的信息。getname()返回类的完整名,
getsuperclass()返回父类的类对象,isinterface()判断
是否是接口。getinterfaces()返回一组类对象,每个类对象
对应一个直接父接口。如果没有,则返回一个长度为零的数组。
getclassloader()返回类加载器的引用,如果是由启动类加载器
加载的则返回null。所有的这些信息都直接从方法区中获得。

方法表
为了提高访问效率,必须仔细的设计存储在方法区中的数据信息
结构。除了以上讨论的结构,jvm的实现者还可以添加一些其他的
数据结构,如方法表。jvm对每个加载的非虚拟类的类型信息中都
添加了一个方法表,方法表是一组对类实例方法的直接引用(包括
从父类继承的方法)。jvm可以通过方法表快速激活实例方法。
(译者:这里的方法表与c++中的虚拟函数表一样,但java方法全都
      是virtual的,自然也不用虚拟二字了。正像java宣称没有
      指针了,其实java里全是指针。更安全只是加了更完备的检查
      机制,但这都是以牺牲效率为代价的,个人认为java的设计者
      始终是把安全放在效率之上的,所有java才更适合于网络开发)

一个例子
为了显示jvm如何使用方法区中的信息,我们据一个例子,我们
看下面这个类:
class lava {
    private int speed = 5; // 5 kilometers per hour
    void flow() {
    }
}

class volcano {
    public static void main(string[] args) {
        lava lava = new lava();

本文关键:jvm
  相关方案
Google
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top