定义:保存在着被加载过的每一个类的信息;这些信息由类加载器在加载类的时候,从类的源文件中抽取出来;static变量信息也保存在方法区中;
可以看做是将类(Class)的元数据,保存在方法区里;
方法区是线程共享的;当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待;
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。jvm也可以允许用户和程序指定方法区的初始大小,最小和最大限制;
方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,这样可能会导致一些类,不再被使用,变为垃圾。这时候需要进行垃圾清理。
方法区内存结构如图所示:
jdk1.8版本后,方法区的实现,默认情况下使用系统内存,并且默认情况下无上限,所以很难观察到方法区内存溢出
import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;public class Demo_03 extends ClassLoader{//可以用来加载类的二进制字节码 public static void main(String[] args) { int j=0; try { Demo_03 test =new Demo_03(); for (int i = 0; i < 10000; i++,j++) { //ClassWriter:生成类的二进制字节码 ClassWriter cw=new ClassWriter(0); //参数意义:版本号,public,类名,包名,父类,接口名称 cw.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/object",null); //返回byte数组 byte[] code = cw.toByteArray(); //执行了类的加载 test.defineClass("Class"+i,code,0,code.length); } }finally { System.out.println(j); } }}
可以在 VM options中设置元空间内存大小格式为:-XX MaxmetaspaceSize=要设置的内存大小
jdk1.8以前的称为永久代,在java中报的错误为:java.lang.OutofMemoryError: PermGen space
jdk1.8版本后称为元空间,在java中报的错误为:java.lang.OutofMemoryError: metaspace
场景
springmybatis 运行时常量池
程序要运行,要编译成二进制字节码,包含:类基本信息,常量池,类方法定义,类方法中包含了虚拟机指令
在idea中使用jdk自带的反编译功能,在终端输入java -v 加上编译后生成的.class文件,可以查看反编译后的详细信息,可以被我们勉强读懂,反编译后的类方法定义中的,会对指令进行查表翻译,这个表就来自与常量池,需要一一对应去寻找
使用javap反编译出来的常量池如图所示:
类方法定义如下图所示:
其中的#7,#13,#15,常量池中都有与之对应
运行时常量池:常量池是*.class文件中的,当该类被加载时,它的常量池信息就会放入运行时常量池,并把里面的符号地址变成真实地址
StringTable StringTable是运行时常量池中比较重要的一个组成部分
面试题:
String s1="a";String s2="b";String s3="a"+"b";String s4=s1+s2;String s5="ab";String s6=s4.intern();//问System.out.println(s3==s4);System.out.println(s3==s5);System.out.println(s3==s6);String x2=new String("c")+new String("d");String x1=""cd";x2.intern(); //问,如果调换了最后两行代码的位置呢,如果是jdk1.6呢 System.out.println(x1==x2);
常量池中的信息,都会被加载到运行时常量池中,这时 a b ab 都是常量池中的符号,还没变为java中的字符串对象
0: ldc #7 // String a
会把a符号变为"a"字符串对象,还会准备好一块空间,就是StringTable(串池),然后去StringTable中找对应的key,没有的话,会把"a"对象放入串池,串池是hashtable结构,不可扩容
对于题中的s4和s5,s4的步骤是
new StringBuilder().append("a").append("b").toString
然后再返回new String(“ab”),s5在串中,而s4在堆中
对于s3和s5,反编译的结果如下图
可以看到s5直接找到"ab"符号,和s3一样,都是到常量池中11号位置找"ab"符号,即s5的"ab"符号在串池中有对应,所以结果为true,所以s3和s4的结果就为false
intern()方法,主动将串池中还没有的字符串对象放入串池,用如下代码解释
public class Demo_07 { //串池中["a","b"] public static void main(String[] args) { String s = new String("a") + new String("b");//new String("ab"),此时ab只在堆中 //堆 new String("a") new String("b")只是和串池中的值相对 String s2 = s.intern();//将这个字符对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回,执行到这里,串池中以及有了"ab" System.out.printLn(s2=="ab");//此时结果为true }}
如果代码如下
public class Demo_07 { //串池中["ab","a","b"] public static void main(String[] args) { String s1 = "ab"; String s = new String("a") + new String("b");//new String("ab"),此时ab只在堆中 //堆 new String("a") new String("b")只是和串池中的值相对 String s2 = s.intern();//将这个字符对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回 System.out.println(s1==s); }}
则结果为false,因为串池中已经先有了"ab",所以s.intern()就放不进去了,这是针对1.8以后的jdk版本
1.6版本的jdk特性为将这个字符对象尝试放入串池,如果有则并不会放入,如果没有则会把对象复制一份,放入串池,然后把串池中的对象返回
总结特性
常量池中的字符串仅是符号,第一次用到时才变为对象(懒惰性)利用串池的机制,来避免重复创建字符串对象字符串常量拼接的原理是编译期优化可以使用intern()方法,主动将串池中还没有的字符串对象放入串池
StringTable位置
如下图:
1.6版本和1.8版本中的StringTable位置不同的原因:永久代内存回收效率低
StringTable 垃圾回收
在idea中编辑配置,在VM options中输入:
这些配置的意义分别为:设置内存大小,打印字符串表的统计信息,打印垃圾回收的详细信息
public class Demo_08 { public static void main(String[] args) { int i=0; try { }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(i); } }}
添加参数后,代码打印信息如图所示
StringTable的统计信息:
StringTable statistics:Number of buckets : 60013 = 480104 bytes, avg 8.000//桶个数Number of entries : 1788 = 42912 bytes, avg 24.000//键值对个数Number of literals : 1788 = 159384 bytes, avg 89.141//串池中的字符串个数Total footprint : = 682400 bytes//总占用空间
StringTable的底层实现类似于HashTable的,HashTable是由数组+链表组成,数组个数称之为桶
现在将代码变成:
public class Demo_08 { public static void main(String[] args) { int i=0; try { for (int j=0;j<100;j++){ String.valueOf(j).intern(); i++; } }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(i); } }}
打印信息变为:
StringTable statistics:Number of buckets : 60013 = 480104 bytes, avg 8.000Number of entries : 1859 = 44616 bytes, avg 24.000Number of literals : 1859 = 162432 bytes, avg 87.376Total footprint : = 687152 bytes
将代码变为:
public class Demo_08 { public static void main(String[] args) { int i=0; try { for (int j=0;j<10000;j++){ String.valueOf(j).intern(); i++; } }catch (Throwable e){ e.printStackTrace(); }finally { System.out.println(i); } }}
打印出来GC有关信息:
[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->720K(9728K), 0.0017699 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 10000
由此可见StringTable会发生垃圾回收
StringTable的性能调优
主要是桶的个数影响,如果桶个数多,性能就好,所以调优主要是调整桶的个数
桶个数设置:在编辑配置里,找到VM options 加上-XX:StringTableSize=桶个数,无单位
桶个数不宜太大,太占空间,太小,费时间
为什么我们非要用StringTable呢?
首先要考虑字符串对象是否入池,如果有重复的对象,使用intern()入池,可以减少内存占用率