`
shenlvcheng
  • 浏览: 17206 次
  • 性别: Icon_minigender_1
  • 来自: 南京
文章分类
社区版块
存档分类
最新评论
阅读更多

方法区
在一个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()方法。

分享到:
评论
2 楼 Spartacus0830 2016-01-26  
你好!这篇文章或者说这本书的英文叫什么?能否告知,谢谢。
365586014@qq.com
1 楼 palagend 2015-10-07  
请问一下,常量池解析中替换的指针lava与压入栈中的指针lava是一回事吗?

相关推荐

    java-内存-方法区介绍

    ### Java内存方法区详解 #### 一、方法区概述 方法区(Method Area),又被称为永久代(PermGen),是Java虚拟机(JVM)的一部分,主要负责存储类的元数据信息,包括但不限于类的class字节码信息、静态与非静态方法、...

    深入讲解Java虚拟机系列之方法区

    深入讲解Java虚拟机系列之方法区 在 Java 虚拟机中,方法区是一块非常重要的区域,它存储了所有类的信息,包括类名、父类、接口、权限修饰符、常量池、变量信息、方法信息、静态变量等。方法区是 Java 虚拟机中的一...

    详谈java 堆区、方法区和栈区

    Java内存模型,也被称为JVM内存区域,是Java运行时数据区域的划分,它将内存分为几个关键部分,包括堆区、方法区和栈区。理解这些区域的工作原理对于优化程序性能和解决内存泄漏等问题至关重要。 1. **堆区**(Heap...

    Util.java 一些公共的Java方法

    在IT领域,尤其是在Java开发中,`Util.java`类扮演着关键角色,它封装了一系列公共方法,用于简化日常编程任务,提高代码复用性。从给定的文件信息来看,`Util.java`主要关注以下几个方面的功能:日期时间处理、字符...

    Java static修饰方法

    1. **存储区**:静态方法存储在方法区,而非堆内存中的对象实例。因此,它们的生命周期从类加载开始,到类卸载结束。 2. **访问限制**:静态方法可以访问类的静态变量和静态方法,但不能直接访问非静态(实例)变量...

    Java多线程 之 临界区、ThreadLocal.docx

    Java多线程编程中,临界区和ThreadLocal是两种重要的并发控制机制,它们用于解决多线程环境下的数据安全问题。 1. **临界区(Critical Section)** 临界区是指一段代码,它在同一时刻只允许一个线程进行访问。在...

    java生成线缓冲区的代码

    本篇文章将深入探讨如何使用Java生成线缓冲区,并结合Eclipse IDE进行开发。 首先,我们需要了解Java中的GIS库,例如JTS(Java Topology Suite)和GeoTools。JTS是一个强大的开源库,提供了一系列的几何操作,包括...

    java处理图片背景颜色的方法

    Java 处理图片背景颜色的方法 Java 处理图片背景颜色的方法是指利用 Java 语言来处理图片的背景颜色,例如将蓝底寸照批量转换为白底。这种方法可以批量处理大量图片,具有很高的实用价值。 title 中的“Java 处理...

    java 内存中 堆、栈、常量池、方法区的总结

    在Java内存管理中,堆(Heap)、栈(Stack)、常量池(Constant Pool)和方法区(Method Area)是四个核心概念,它们在Java程序运行时扮演着不同的角色。 首先,方法区是用来存放类的信息、常量、静态变量等数据的...

    利用缓冲区提高Java应用程序的IO性能

    ### 利用缓冲区提高Java应用程序的IO性能 #### 摘要与背景介绍 Java作为一门具有跨平台特性的编程语言,在多个领域都获得了广泛的应用。随着Java应用的不断扩展,其性能问题逐渐成为人们关注的重点,尤其是输入...

    SuperMap Objects Java线对象缓冲区分析

    在这个特定的案例"SuperMap Objects Java线对象缓冲区分析"中,我们将深入探讨如何在Java环境下利用SuperMap Objects进行线对象的缓冲区分析。 首先,我们要理解什么是缓冲区分析。缓冲区分析是GIS中的一个基本操作...

    JAVA常用类和方法

    JAVA常用类和方法 JAVA语言中有许多常用的类和方法,以下是其中的一些: 字符串类 字符串是JAVA中最基本的数据类型之一。String类提供了许多有用的方法,以下是一些常用的方法: * 获取字符串的长度:length() *...

    Java技术与Java虚拟机

    JVM的内部结构分为五个主要部分:方法区、堆、Java栈、程序计数器和本地方法栈。方法区存储类信息,堆存放对象,每个线程有自己的Java栈和程序计数器,本地方法栈处理本地方法调用。 JVM执行指令的方式非常直接,...

    一种基于Java语言的LiDAR点云数据处理方法

    通过对某测区的Li DAR点云数据进行处理,并比较了不同语言环境下的处理结果,验证了在Java语言环境下该方法进行Li DAR点云数据处理的可行性,以及利用该方法引入树结构的网格空间索引在Java语言环境下的实现性。

    Java代码实现随机生成汉字的方法

    Java代码实现随机生成汉字的方法 Java代码实现随机生成汉字的方法是指利用Java语言编写的程序来随机生成汉字。该方法可以生成随机的汉字,满足特定的需求。 背景知识 -------- GB 2312-80 是中国国家标准简体中文...

    JAVA核心面试知识点整理

    JVM的运行时内存可以分为五个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区。 1. 线程 在Java中,线程是JVM的基本执行单元。Java线程可以分为两种:用户线程和守护线程。用户线程是用户自己创建的线程,而...

    java源码包---java 源码 大量 实例

     Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...

    JAVA JAVA JAVA JAVA JAVA JAVA JAVA

    6. **JVM**:Java虚拟机是Java程序运行的平台,理解内存模型(堆、栈、方法区等)、垃圾回收机制、JVM调优对提高程序性能有直接影响。 7. **泛型**:引入泛型可以提供编译时类型安全,并减少在类和集合中使用的类型...

    深入java虚拟机.pdf

    Java 虚拟机的内存结构包括方法区(method area)和堆(heap)。方法区保存了从类文件中解析出来的信息。堆保存了程序执行时创建的对象。每一个线程都有自己的 PC 寄存器(程序计数器)和 Java 堆栈(Java stack)。...

Global site tag (gtag.js) - Google Analytics