精华内容
下载资源
问答
  • java方法区

    千次阅读 2020-06-05 15:54:35
    Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non- Heap (非堆),目的...

    栈、堆、方法区的交互关系

    在这里插入图片描述
    在这里插入图片描述

    方法区理解

    《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non- Heap (非堆),目的就是要和堆分开。
    所以,方法区看作是一块独立于Java堆的内存空间。

    • 方法区(Method Area) 与Java堆一样,是各个线程共享的内存区域
    • 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的
    • 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。.
    • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误: java.lang .OutofMemoryError:PermGenspace(1.8之前)或者java.lang.OutOfMemoryError: Metaspace(1.8之后),关闭JVM就会释放这个区域的内存。例如:加载大量的第三方的jar包; Tomcat 部署的工程过多(30-50个) ;大量动态的生成反射类都有可能OOM
      在这里插入图片描述

    jdk7及以前,习惯上把方法区,称为永久代jdk8开始, 使用元空间取代了永久代。本质上,方法区和永久代并不等价。仅是对hotspot而言的。
    《Java 虚拟机规范》对如何实现方法区,不做统一要求。例如: BEA JRockit/ IBM J9中不存在永久代的概念。现在来看,当年使用永久代,不是好的idea。 导致Java程序更容易OOM (超过-XX : MaxPermSize上限)
    元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。本地内存就不容易出现OOM,但还是会
    永久代、元空间二者并不只是名字变了,内部结构也调整了。
    根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出O0M异常:java.lang.OutofMemoryError:PermGenspace(1.8之前)或者java.lang.OutOfMemoryError: Metaspace(1.8之后)

    内部结构

    在这里插入图片描述
    《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
    在这里插入图片描述

    • 类型信息
      对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
      ①这个类型的完整有效名称(全名=包名.类名)
      ②这个类型直接父类的完整有效名(对于interface或是java . lang . object,都没有父类)
      ③这个类型的修饰符(public, abstract, final的某个 子集)
      ④这个类型直接接口的一个有序列表
    • 域(Field)信息
      JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。域的相关信息包括: 域名称、 域类型、域修饰符(public, private, protected, static, final, volatile, transient的某个子集)
    • 方法(Method)信息
      JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:方法名称、方法的返回类型(或void)、方法参数的数量和类型(按顺序)、方法的修饰符(public, private, protected,static,final,synchronized,native, abstract的一个子集)、方法的字节码(bytecodes)、操作数栈、局部变量表及大小( abstract和native,方法除外)、异常表( abstract和native方法除外)每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
    • non-final的类变量
      静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。类变量被类的所有实例共享,即使没有类实例时你也可以访问它。
      补充说明:全局常量(static final):被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。

    运行时常量池vs常量池

    方法区内部包含了运行时常量池。字节码文件内部包含了常量池。要弄清楚方法区,需要理解清楚ClassFile,因为加载类的信息都在方法区。要弄清楚方法区的运行时常量池,需要理解清楚ClassFile中的常量池。

    常量池

    .java经过编译后生成的.class文件,是Class文件的资源仓库,一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表(Constant Pool Table) ,包括各种字面量和对类型、域和方法的符号引用。
    在这里插入图片描述
    一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池(包含:数量值、字符串值、类引用、字段引用、方法引用),这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池,之前有介绍。

    常量池:可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
    

    运行时常量池

    运行时常量池( Runtime Constant Pool) 是方法区的一部分。
    常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译期间生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池。
    JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。
    运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。运行时常量池,相对于Class文件常量池的另一重要特征是:具备动态性。而运行时常量池期间也有可能加入新的常量(如:String.intern方法)当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则JVM会抛OutOfMemoryError异常。

    设置方法区内存大小

    方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。

    • jdk7及以前: 通过-XX:PermSize来设置永久代初始分配空间。默认值是20.75M,-XX :MaxPe rmSi ze来设定永久代最大可分配空间。32位机器默认是64M,64位机 器模式是82M,当JVM加载的类信 息容量超过了这个值,会报异常Outo fMemoryError : PermGen space

    • jdk8及以后: 元数据区大小可以使用参数-XX:MetaspaceSize和-XX :MaxMetaspaceSize指定,替代上述原有的两个参数。
      默认值依赖于平台。windows下,-XX :MetaspaceSize是21M, -XX :MaxMetaspaceSize的值是-1,即没有限制与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存。如果元数据区发生溢出,虚拟机一样会抛出异常OutOfMemoryError: Metaspace
      -XX:MetaspaceSize: 设置初始的元空间大小。对于64位的服务器端JVM来说, (其默认的-XX:MetaspaceSize值为21MB。这就是初始的高水位线,一 旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。如果初始化的高水位线设置过低,上述高水位 线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC,建议将-XX :MetaspaceSize设置为一个相对较高的值。

    方法区演变细节

    首先明确:只有HotSpot才有永久代。BEA JRockit、 IBM J9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一-。 Hotspot中方法区的变化如下图:

    在这里插入图片描述

    为什么永久代要被替换为元空间?

    随着Java8的到来,HotSpot VM中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域叫做元空间( Metaspace )。
    由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。
    这项改动是很有必要的,原因有:
    为永久代设置空间大小是很难确定的
    在某些场景下,如果动态加载类过多,容易产生Perm区的O0M。比如某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。默认值其实差点意思空间也不大,这个空间设置的小了,就会出现fullGC,fullGC的代价是很大的STW时间大,影响系统性能,而且可能fullGC完了后这些类可能还会被使用,那就会出现OOM导致程序终止,分配大了又觉得浪费。而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
    对永久代进行调优是很困难的

    StringTable为什么要调整?

    jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在fullgc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发这就导致str ingTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

    方法区的垃圾回收

    有些人认为方法区(如HotSpot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其实不然。《Java 虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(如JDK 11时期的ZGC收集器就不支持类卸载)。

    一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前Sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。
    方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量和不再使用的类型。

    判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:

    1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
    2. 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    

    Java虚拟机被允许对满足上述三个条件的无用类进行回收,**这里说的仅仅是"被允许”,而并不是和对象一样,没有引用了就必然会回收。**关于是否要对类型进行回收,HotSpot虛拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading查 看类加载和卸载信息
    在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机县备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

    展开全文
  • java方法区究竟存储了什么

    万次阅读 多人点赞 2016-05-14 13:40:46
    首先要说明的是,此文章转载自 ...谢谢作者。另外,这里ps一下,Class对象是存放在堆区的,不是方法...Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)才是存在方法区

    首先要说明的是,此文章转载自
    http://blog.csdn.net/zzhangxiaoyun/article/details/7518917
    谢谢作者。另外,这里ps一下,Class对象是存放在堆区的,不是方法区!这点很多人容易犯错。类的元数据(元数据并不是类的Class对象!Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)才是存在方法区的!

    方法区
    在一个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方法区详解

    万次阅读 多人点赞 2019-02-13 12:07:27
    方法区 保存在着被加载过的每一个类的信息;这些信息由类加载器在加载类的时候,从类的源文件中抽取出来;static变量信息也保存在方法区中; 可以看做是将类(Class)的元数据,保存在方法区里; 方法区是线程...

     

    方法区

    保存在着被加载过的每一个类的信息;这些信息由类加载器在加载类的时候,从类的源文件中抽取出来;static变量信息也保存在方法区中;

    可以看做是将类(Class)的元数据,保存在方法区里;

    方法区是线程共享的;当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待;

    方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。jvm也可以允许用户和程序指定方法区的初始大小,最小和最大限制;

    方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,这样可能会导致一些类,不再被使用,变为垃圾。这时候需要进行垃圾清理。
     

    图例(方法区中都保存什么)

    è¿éåå¾çæè¿°

    类型信息

    包括以下几点:

    类的完整名称(比如,java.long.String)
    类的直接父类的完整名称
    类的直接实现接口的有序列表(因为一个类直接实现的接口可能不止一个,因此放到一个有序表中)
    类的修饰符
    可以看做是,对一个类进行登记,这个类的名字叫啥,他粑粑是谁、有没有实现接口, 权限是啥;
     

    类型的常量池 (即运行时常量池)

    每一个Class文件中,都维护着一个常量池(这个保存在类文件里面,不要与方法区的运行时常量池搞混),里面存放着编译时期生成的各种字面值和符号引用;这个常量池的内容,在类加载的时候,被复制到方法区的运行时常量池 ;

    字面值:就是像string, 基本数据类型,以及它们的包装类的值,以及final修饰的变量,简单说就是在编译期间,就可以确定下来的值;

    符号引用:不同于我们常说的引用,它们是对类型,域和方法的引用,类似于面向过程语言使用的前期绑定,对方法调用产生的引用;

    存在这里面的数据,类似于保存在数组中,外部根据索引来获得它们 ;
     

    字段信息

    • 声明的顺序
    • 修饰符
    • 类型
    • 名字

     

    方法信息

    • 声明的顺序
    • 修饰符
    • 返回值类型
    • 名字
    • 参数列表(有序保存)
    • 异常表(方法抛出的异常)
    • 方法字节码(native、abstract方法除外,)
    • 操作数栈和局部变量表大小

    类变量(即static变量)

    非final类变量

    在java虚拟机使用一个类之前,它必须在方法区中为每个非final类变量分配空间。非final类变量存储在定义它的类中;

    final类变量(不存储在这里)

    由于final的不可改变性,因此,final类变量的值在编译期间,就被确定了,因此被保存在类的常量池里面,然后在加载类的时候,复制进方法区的运行时常量池里面 ;final类变量存储在运行时常量池里面,每一个使用它的类保存着一个对其的引用;
     

    对类加载器的引用

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

    对Class类的引用

    jvm为每个加载的类都创建一个java.lang.Class的实例(存储在堆上)。而jvm必须以某种方式把Class的这个实例和存储在方法区中的类型数据(类的元数据)联系起来, 因此,类的元数据里面保存了一个Class对象的引用;


    方法表


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

    JVM如何使用方法区里面的数据


    一个例子 
    为了显示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()方法。

     

    转载自:https://blog.csdn.net/youngyouth/article/details/79933612

    展开全文
  • java方法区存储了什么

    千次阅读 2018-03-23 21:10:48
    首先要说明的是,此文章转载自 ...Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)才是存在方法区的!...

    首先要说明的是,此文章转载自 
    http://blog.csdn.net/zzhangxiaoyun/article/details/7518917 
    谢谢作者。另外,这里ps一下,Class对象是存放在堆区的,不是方法区!这点很多人容易犯错。类的元数据(元数据并不是类的Class对象!Class对象是加载的最终产品,类的方法代码,变量名,方法名,访问权限,返回值等等都是在方法区的)才是存在方法区的!

    方法区 
    在一个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 方法区是在堆里面

    千次阅读 2019-01-08 11:19:10
    1、 java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变; 2、 java7中,static变量从永久代移到堆中; 3、 java8中,取消永久代,方法区存放于元空间...
  • java方法区和常量池

    千次阅读 2018-07-04 17:55:11
     和java堆一样,方法区也是属于线程共享的区域,存放的是java的类型信息,静态变量,运行时常量池以及jit编译后的代码等数据。  运行时常量池:  class文件中除了包含类的版本,类名,字段,方法,接口等信息,...
  • JAVA 方法区存放

    千次阅读 2019-02-21 14:13:34
    1、 java1.7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变; 2、 java1.7中,static变量从永久代移到堆中; 3、 java1.8中,取消永久代,方法存放于元...
  • Java内存区域介绍(附带JDK1.8后方法区的变化)

    千次阅读 多人点赞 2019-07-13 13:51:23
    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(JavaSE7版)》的规定,java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示 程序计数器...
  • java虚拟机结构图解(堆栈方法区)

    千次阅读 2020-05-11 10:55:24
    线程私有:比如我除了main线程还另外写了一个Test线程,他们同时在执行,那么我们内存就会出现两个线程栈(即图中右边紫色的部分各来一份,全部整到一起,)图中左边即为两个线程栈,每个线程栈里面的都若干个栈帧...
  • Java 虚拟机栈(后面简称栈)是线程私有的,所以他的生命周期与当前线程是一样的,栈是用来描述方法执行的一个内存模型,因为每个方法在执行的同时,都会创建一个栈帧,而这个栈帧里面,又存储着局部变量表,操作数...
  • 状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...
  • java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变; java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在...
  • JVM - 2. java堆栈里面到底有什么

    千次阅读 2017-12-27 10:55:35
    在虚拟机自动内存管理机制的帮助下, 不... Java虚拟机定义了若干种程序运行期间会使用到的运行时数据,其中一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。还有一些则是与线程一一对应,他们的生命周期也随着
  • 大多数JVM将内存分配为Method Area(方法区)、Heap(堆)、Program Counter Register(程序计数器)、JAVA Method Stack(JAVA方法栈)、Native Method Stack(本地方法栈)。 2、 方法区(Method Area) 线程共享,存储...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    原理是什么Java语言哪些特点什么是字节码?采用字节码的最大好处是什么什么Java程序的主类?应用程序和小程序的主类何不同?Java应用程序与小程序之间那些差别?Java和C++的区别Oracle JDK 和 OpenJDK 的...
  • JDK1.7 及之后版本的 JVM 已经将运行时...JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。 已知: String A="abc"; String B="abc"; String C=new String("abc"); String D=new Str...
  • JAVA】堆、栈与方法区

    千次阅读 2019-10-25 13:41:25
    2、JVM内存分为堆(heap)、栈(stock)、方法区(method)三个区域,分别用于储存不同的数据。 3、HotSpot是Sun JDK和Open JDK中所带的虚拟机(Sun JDK和Open JDK除了注释,代码实现基本上是相同的)。 下面我们来...
  • Java方法区和永久代

    千次阅读 2019-03-08 00:09:56
    目前三大Java虚拟机:HotSpot,oracle JRockit,IBM J9。 JRockit是oracle发明的,用于其WebLogic服务器,IBM JVM是IBM发明的用于其Websphere服务器(所以在某行开发的时候,他们用的是IBM的JDK,因为他们使用的...
  • Java方法区、永久代、元空间、常量池详解

    万次阅读 多人点赞 2018-07-14 18:55:20
    1.JVM内存模型简介 堆——堆是所有线程共享的,主要... Java虚拟机栈/本地方法栈——线程私有的,主要存放局部变量表,操作数栈,动态链接和方法出口等; 程序计数器——同样是线程私有的,记录当前线程的行号...
  • 百度上的答案太多,说的都比较抽象,请假大神们给我举例说明下 我自己的理解: 堆:存放对象、数组 栈:存放基础数据类型的对象和自定义对象的引用(不是对象) 方法区:存放class文件、常量、静态变量
  • Java内存区域——堆,栈,方法区

    千次阅读 多人点赞 2018-01-02 16:15:48
    方法区 运行时常量池 拓展 运行时数据区域 jdk1.7中, Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。 程序计数器 1. 程序计数器(Program Counter ...
  • 从 JDK 1.7 开始,Oracle 团队就开始对 HotSpot VM 的永久代(PermGen)大刀阔斧的修改、移除,导致 HotSpot 的内存区域发生了很多改变,最终在 JDK 1.8 元空间(Metaspace)取代了永久代成为 HotSpot VM 对方法区的...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • 《深入理解 java 虚拟机》 读书扩展 作者:淮左白衣 写于 2018年4月13日21:26:05 目录 ...图例(方法区中都保存什么) ...(摘抄)JVM如何使用方法区里面的数据的 参考: Java方法区 方...
  • 对于字符串常量,根据JDK版本的不同,的放到了方法区的没有。 方法区中存放的是类型信息、常量、静态变量、即时编译器编译后的代码缓存、域信息、方法信息等。随着JDK的发展,方法区中存放的内容也在发生变化...
  • java 内存之方法区详解

    千次阅读 2017-05-09 10:23:16
    Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 (1) 在一个 JVM 实例的内部,类型信息都会被存储在一个称为方法区的内存逻辑区中。...
  • java存储机制(栈、堆、方法区详解)

    万次阅读 多人点赞 2016-11-01 16:05:06
    一、java的六种存储地址及解释 1) 寄存器(register):这是最快的存储,因为它位于不同于其他存储的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 276,770
精华内容 110,708
关键字:

java方法区里面有什么

java 订阅