首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

转:JVM 步骤区

2012-10-28 
转:JVM 方法区在一个jvm实例的内部,类信息被存储在一个称为方法区的内存逻辑区中。类信息是由类加载器在类

转:JVM 方法区

在一个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();?
??????? lava.flow();?
??? }?
}?
下面我们描述一下main()方法的第一条指令的字节码是如何被执行的。不同的jvm实现的差别很大,这里只是其中之一。?

为了运行这个程序,你以某种方式把“Volcano"传给了jvm。有了这个名字,jvm找到了这个类文件(Volcano.class)并读 入,它从?
类文件提取了类信息并放在了方法区中,通过解析存在方法区中的字节码,jvm激活了main()方法,在执行时,jvm保持了一个指向当前类 (Volcano)常量池的指针。?

注意jvm在还没有加载Lava类的时候就已经开始执行了。正像大多数的jvm一样,不会等所有类都加载了以后才开始执行,它只会在需要的时候才 加载。?

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

这个符号引用仅仅是类lava的完整有效名”lava“。这里我们看到为了jvm能尽快从一个名称找到一个类,一个良好的数据结构是多么重要。这 里jvm的实现者可以采用各种方法,如hash表,查找树等等。同样的算法可以用于Class类的forName()的实现。?

当jvm发现还没有加载过一个称为"Lava"的类,它就开始查找并加载类文件"Lava.class"。它从类文件中抽取类信息并放在了方法 区中。?

jvm于是以一个直接指向方法区lava类的指针替换了常量池第一项的符号引用。以后就可以用这个指针快速的找到lava类了。而这个替换过程称 为常量池解析(constant pool resolution)。在这里我们替换的是一个native指针。?

jvm终于开始为新的lava对象分配空间了。这次,jvm仍然需要方法区中的信息。它使用指向lava数据的指针(刚才指向volcano常量 池第一项的指针)找到一个lava对象究竟需要多少空间。?

jvm总能够从存储在方法区中的类信息知道某类对象需要的空间。但一个对象在不同的jvm中可能需要不同的空间,而且它的空间分布也是不同 的。(译者:这与在C++中,不同的编译器也有不同的对象模型是一个道理)?

一旦jvm知道了一个Lava对象所要的空间,它就在堆上分配这个空间并把这个实例的变量speed初始化为缺省值0。假如lava的父对象也有 实例变量,则也会初始化。?

当把新生成的lava对象的引用压到栈中,第一条指令也结束了。下面的指令利用这个引用激活java代码把speed变量设为初始值,5。另外一 条指令会用这个引用激活Lava对象的flow()方法。

?


热点排行