1、方法区-Method Area
线程共享,存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等等。(HotSpot虚拟机上开发部署人员更愿意成为“永久代”,Permanent Generation)。
1.1、类型信息
- 类型的全限定名
- 超类的全限定名
- 直接超接口的全限定名
- 类型标志(该类是类类型还是接口类型)
- 类的访问描述符(public、private、default、abstract、final、static)
1.2、类型的常量池
(该部分是独有的,然后运行时,把该部分加载进运行时常量池,当调用时则从符号引用解析为直接引用,但是有些确定的方法会直接转换,比如静态方法,比如构造方法)
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的符号引用,所以它也是动态连接(栈中对应的方法指向这个引用)的主要对象(在动态链接中起到核心作用)。
1.3、字段信息(该类声明的所有字段)
- 字段修饰符(public、protect、private、default)
- 字段的类型
- 字段名称
1.4、方法信息
方法信息中包含类的所有方法,每个方法包含以下信息:
- 方法名
- 方法的返回类型(包括void)
- 方法参数的类型、数目以及顺序
- 方法修饰符(public,private,protected,static,final,synchronized,native,abstract)
针对非本地方法,还有些附加方法信息需要存储在方法区内:
- 方法字节码
- 方法中局部变量区的大小、方法栈帧
- 异常表
1.5、类变量(静态变量)
指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。
1.6、指向类加载器的引用
每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
1.7、指向Class实例的引用
类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过Class.forName(StringclassName)来查找获得该实例的引用,然后创建该类的对象。
1.8、方法表
为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的,类似C++虚函数表vtbl。
2、堆-Heap
JVM内存底层结构:
- VM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。
- 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
2.1、JDK1.8之前
一个对象被创建以后首先被放到的Eden内存中,如果存活期超两个Survivor之后就会被转移到长时内存中(Old Generation)中。永久内存中存放着对象的方法、变量等元数据信息。如果永久内存不够,会发生错误:java.lang.OutOfMemoryError: PermGen
2.1、JDK1.8之后
把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)中。
永久内存就不再占用堆内存,它可以通过自动增长来避免JDK7以及前期版本中常见的永久内存错误(java.lang.OutOfMemoryError: PermGen)。当然JDK8也提供了一个新的设置Matespace内存大小的参数,通过这个参数可以设置Matespace内存大小,这样我们可以根据自己项目的实际情况,避免过度浪费本地内存,达到有效利用。