方法区
在一个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()方法。
分享到:
相关推荐
- 成员变量存储在堆中的对象里面,由垃圾回收器负责回收。 - 局部变量存储在栈中,随着方法的消失而消失。 - 示例代码: ```java class BirthDate { private int day; private int month; private int year...
当JVM加载一个类时,如果该类包含`static`修饰的成员变量和方法,JVM会在固定内存位置为其分配空间,这使得它们在整个类的生命周期中都可以被快速访问。静态成员变量和方法不会随着对象的创建和销毁而改变,它们属于...
#### 二、static变量 ##### 1. 静态变量(类变量) - **定义**:被`static`修饰的变量称为静态变量或类变量。 - **特点**: - 内存中只有一个拷贝,节省内存空间。 - 在类加载过程中,JVM只为其分配一次内存。 ...
在Android开发中,静态变量(`static`)的使用是一个重要的议题,因为它涉及到应用程序的内存管理、生命周期以及可能引发的问题。下面将详细讨论以下几个方面: 1. **静态变量的生命周期**: - 静态变量在类被加载...
在堆内存中,有一个特殊的区域叫永久代( Perm Gen space),在较旧的JVM版本中,用于存储类元数据。在现代JVM(如Java 8及更高版本)中,这部分功能已被元空间(Metaspace)取代,它位于本地内存,以减少内存溢出...
3. **存储位置**:成员变量存储在堆内存的对象中;静态变量存储在方法区的静态区。 4. **别名**:成员变量也称为实例变量;静态变量也称为类变量。 在实际编程中,理解这些变量的特点及其差异对于编写高效、清晰的...
- 类的加载是通过类加载器完成的,它会找到类的字节码文件(.class),然后解析并加载到JVM中。 - 初始化发生在类首次被使用时,如创建类的实例、访问静态字段或调用静态方法。 5. **内存效率与优化**: - 使用`...
2. **存储位置**:静态成员存储在方法区的静态区域,而非静态成员存储在堆空间的对象实例中。 3. **访问方式**:静态成员可以不通过对象直接通过类名访问,而非静态成员必须通过对象实例访问。 4. **引用限制**:...
在Java中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到JVM中,至于其他类,则在需要的时候才加载。 类加载的主要步骤包括: - 装载:根据查找路径...
在某些JVM实现中,它与Java虚拟机栈合并在一起。 ##### 4.5 方法区(Method Area) 方法区存储了每个类的信息(包括类的方法和字段)、常量池、静态变量等。它是共享的内存区域,用于存放所有类的信息。 #### 五、...
- **方法区(Method Area)**:存储类信息,如类名、方法信息、常量池(包括static常量和static变量)以及编译后的字节码。在Java 8中,永久代被MetaSpace取代。 - **堆区(Heap)**:用于存储所有对象实例和数组...
在深入探讨JVM性能优化相关面试题之前,先了解Java虚拟机(JVM)中类加载过程、JVM加载Class文件的原理机制以及Java内存分配的知识点是非常有必要的。 首先,Java类加载过程共有七个步骤,这七个步骤分别是:加载、...
环境变量是在操作系统中存储特定信息的变量,比如`JAVA_HOME`指向Java开发工具的安装路径,`PATH`包含了系统能够找到可执行文件的目录路径。在Java的上下文中,`JAVA_HOME`和`PATH`是两个非常关键的环境变量。 1. *...
通过对JVM的理解,我们可以了解到Java程序是如何在JVM中运行的,以及JVM如何管理内存和执行字节码。这对于优化Java程序性能、解决内存泄漏等问题至关重要。掌握JVM的核心概念及其内部工作原理,对于提高Java开发者的...
在Java编程语言中,`static`关键字是一个非常重要的修饰符,它有多种用途,涉及到类、对象以及内存管理等多个方面。下面将详细说明`static`关键字的使用方法和适用场合。 1. **静态变量(Static Variables)** - `...
- 当局部内部类需要访问所在方法的局部变量时,JVM会自动为这些局部变量创建副本,并存储在局部内部类的隐式成员变量中。 - 这一行为仅适用于final修饰的局部变量。 #### 示例分析 考虑以下代码示例: ```java ...
1. **资源消耗**:静态方法和变量存储在方法区,如果过度使用静态,可能会增加内存占用,影响性能。 2. **非线程安全**:静态变量如果不进行同步控制,在多线程环境下可能导致数据不一致。 3. **破坏封装**:静态...
静态变量存储在方法区中,对于所有的实例来说,静态变量是共享的。因此,更改一个对象的静态变量会影响到所有对象。 #### 三、static修饰方法 `static`修饰的方法称为静态方法,静态方法可以直接通过类名来调用,...
- **方法区(Method Area)**:存储类信息、常量池(包括static常量和static变量)、编译后的字节码等数据。在Java 8中,方法区被替换为Metaspace。 - **堆区(Heap)**:用于存储对象实例和数组,包括新生代...