JVM方法区
- 方法区的用途: 主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
PermGen(永久代)
- 什么是永久代
永久代是方法区的一种实现,只有Java1.7之前的HotSpot虚拟机才有永久代,对于其他类型的虚拟机并没有永久代,方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。
在JDK1.8中HotSpot已经没有永久代这个区间,取而代之的是一个叫做Metaspace(元空间)的东西。
Metaspace(元空间)
- 什么是元空间
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
Java8之后元数据区还保存着什么
在jdk7之后方法区四分五裂,不再是单一的在一个区域内进行存储。其中,符号引用存储在native heap中,字符串常量和静态类型变量存储在普通的堆区中,这个影响了String的intern()方法的行为。
java8依照java7的划分方法,在元数据区只存储类和类加载器的元数据信息。暂时理解为静态常量池。
Java常量池
常量池的分类
- 静态常量池
- 运行时常量池
静态常量池
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:
- 类和接口的全限定名
- 字段名称和描述符
- 方法名称和描述符
运行时常量池
- 运行时常量池简介
运行时常量池指jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。
-
常量池的优点
- 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
- 在编译阶段就把所有的字符串文字放到一个常量池中。节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。节省运行时间:比较字符串时,比equals()快。对于两个引用变量,只用判断引用是否相等,也就可以判断实际值是否相等。(这个基本没用)
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true 1
System.out.println(s1 == s3); // true 2
System.out.println(s1 == s4); // false 3
System.out.println(s1 == s9); // false 4
System.out.println(s4 == s5); // false 5
System.out.println(s1 == s6); // true 6
- 两个变量均在运行时常量池中。
- 编译器会将"Hel"和"lo"优化为"Hello"。转化成1的情况
- new String(“lo”)在堆中创建所以地址不同。
- s7与s8为变量,编译器不知道他们具体是什么,所以不会像2一样优化
- s4与s5均为在堆中创建。
- s5.intern()会将该字符串转化为常量池中的地址。
除了字符串常量池之外的其他运行时常量池
整型常量池、浮点型常量池(java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;两种浮点数类型的包装类Float,Double并没有实现常量池技术) 等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,(Byte,Short,Integer,Long,Character,Boolean)这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。