方法区究竟是个什么鬼

1、方法区与永久代

这两个是很是容易混淆的概念,永久代的对象放在方法区中,就会想固然地认为,方法区就等同于持久代的内存区域。事实上二者是这样的关系:html

《Java虚拟机规范》只是规定了有方法区这么个概念和它的做用,并无规定如何去实现它。那么,在不一样的 JVM 上方法区的实现确定是不一样的了。 同时大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。换句话说:方法区是一种规范,永久代是Hotspot针对这一规范的一种实现。而永久代自己也在迭代中:java

在Java 6中,方法区中包含的数据,除了JIT编译生成的代码存放在native memory的CodeCache区域,其余都存放在永久代;
在Java 7中,Symbol的存储从PermGen移动到了native memory,而且把静态变量从instanceKlass末尾(位于PermGen内)移动到了java.lang.Class对象的末尾(位于普通Java heap内);
在Java 8中,永久代被完全移除,取而代之的是另外一块与堆不相连的本地内存——元空间(Metaspace),‑XX:MaxPermSize 参数失去了意义,取而代之的是-XX:MaxMetaspaceSize。

对于Java8, HotSpots取消了永久代,那么是否是也就没有方法区了呢?固然不是,方法区是一个规范,规范没变,它就一直在。那么取代永久代的就是元空间。它与永久代有什么不一样的?数组

存储位置不一样,永久代是堆的一部分,和新生代,老年代地址是连续的,而元空间属于本地内存;框架

存储内容不一样,元空间存储类的元信息,静态变量和常量池等并入堆中。至关于永久代的数据被分到了堆和元空间中。jvm

2、方法区里存着什么?

既然永久代是方法区的一种实现,那么在Hotspot下,方法区就等于永久代,也被称为非堆。那方法区里都存着什么呢?先抛结论:性能

静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池存在方法区中 。spa

类信息与类常量池

方法区里的class文件信息包括:魔数,版本号,常量池,类,父类和接口数组,字段,方法等信息,其实类里面又包括字段和方法的信息。
在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于肯定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,因为常量的数量是不固定的,因此常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值,参见下图。.net

clipboard.png

所以class文件信息和class文件常量池的关系以下图:code

clipboard.png

图中还包含一个运行时常量池,这个稍后会介绍。htm

class文件常量池中存储了哪些内部呢?

咱们写的每个Java类被编译后,就会造成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各类字面量(Literal)和符号引用(Symbolic References);

每一个class文件都有一个class常量池。

动态常量池

运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来讲具有动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池能够在运行期间将符号引用解析为直接引用。能够说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,经过这个索引最终可获得该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。

静态常量池和动态常量池的关系以及区别

静态常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。

动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,好比说类的静态方法或私有方法,实例构造方法,父类方法,这是由于这些方法不能被重写其余版本,因此能在加载的时候就能够将符号引用转变为直接引用,而其余的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

本节总结

方法区里存储着class文件信息和动态常量池,class文件的信息包括类信息和静态常量池。能够将类的信息是对class文件内容的一个框架,里面具体的内容经过常量池来存储。

动态常量池里的内容除了是静态常量池里的内容外,还将静态常量池里的符号引用转变为直接引用,并且动态常量池里的内容是能动态添加的。例如调用String的intern方法就能将string的值添加到String常量池中,这里String常量池是包含在动态常量池里的,但在jdk1.8后,将String常量池放到了堆中。

3、jvm中的常量池

在Java的内存分配中,总共3种常量池:

字符串常量池(String Constant Pool):

字符串常量池在Java内存区域的哪一个位置?
在JDK6.0及以前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;

在JDK7.0版本,字符串常量池被移到了堆中了。至于为何移到堆内,大概是因为方法区的内存空间过小了。

字符串常量池是什么?

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每一个HotSpot VM的实例只有一份,被全部的类共享。字符串常量由一个一个字符组成,放在了StringTable上。

在JDK6.0中,StringTable的长度是固定的,长度就是1009,所以若是放入String Pool中的String很是多,就会形成hash冲突,致使链表过长,当调用String#intern()时会须要到链表上一个一个找,从而致使性能大幅度降低;

在JDK7.0中,StringTable的长度能够经过参数指定:

-XX:StringTableSize=66666

字符串常量池里放的是什么?

在JDK6.0及以前版本中,String Pool里放的都是字符串常量;

在JDK7.0中,因为String#intern()发生了改变,所以String Pool中也能够存放放于堆内的字符串对象的引用。关于String在内存中的存储和String#intern()方法的说明,能够参考个人另一篇博客:

须要说明的是:字符串常量池中的字符串只存在一份!

如:

String s1 = "hello,world!";
String s2 = "hello,world!";

即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2.

class常量池(Class Constant Pool):

咱们写的每个Java类被编译后,就会造成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各类字面量(Literal)和符号引用(Symbolic References);

每一个class文件都有一个class常量池。

运行时常量池(Runtime Constant Pool):

运行时常量池存在于内存中,也就是class常量池被加载到内存以后的版本,不一样之处是:它的字面量能够动态的添加(String#intern()),符号引用能够被解析为直接引用

JVM在执行某个类的时候,必须通过加载、链接、初始化,而链接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每一个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是咱们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

常量池的好处

常量池是为了不频繁的建立和销毁对象而影响系统性能,其实现了对象的共享。

例如字符串常量池,在编译阶段就把全部的字符串文字放到一个常量池中。

(1)节省内存空间:常量池中全部相同的字符串常量被合并,只占用一个空间。

(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就能够判断实际值是否相等。

参考文档

https://www.zhihu.com/questio...

http://blog.csdn.net/vegetabl...

https://www.cnblogs.com/holos...

https://blog.csdn.net/vegetab...

https://blog.csdn.net/u011635...

相关文章
相关标签/搜索