常量池: 就是一张常量表,虚拟机指令根据常量表找到要执行的类名,方法名,参数类型,字面量等信息
可以提高程序的性能和内存效率,避免重复创建相同内容的对象
一个类运行,要先编译成字节码
字节码组成: 类基本信息,常量池,类方法定义(包含 虚拟机指令)
可以用javap反编译字节码文件
javap -v HelloWorld.class
常量池有四种类型
静态常量池: 静态常量池是.class文件中的,包含了编译期生成的各种字面量和符号引用,
如字符串、数字、类名、方法名等。静态常量池在编译时就被确定,不会在运行时改变。
运行时常量池: 是存在.class文件中的,是每个类或接口的静态常量池在运行时的表现形式
当类被加载,它的常量池信息会被放入运行时常量池,
运行时也可以动态添加新的常量(如String类的intern()方法)
并把里面的符号地址变为真实地址
常量池中信息被加载到运行时常量池中,此时,信息还没有成为java中的字符串对象
只有在调用这个字符串变量的时候,才会将信息变成对象 //懒惰
创建对象时,先在 串池StringTable 中查找,
如果没有在串池中创建新的,如果有使用串池中对象
串池是HashTable哈希表结构的
String s1="a";
String s2="b";
STring s3="ab";
String s4=s1+s2;
//此处调用了s1和s2,先查找串池,串池中没有,在串池中创建a和b
//然后底层对字符串变量相加做了优化,用了StringBuilder,和append方法
//然后new了一个新字符串
//但是new的对象都是存在堆中的
sout(s3==s4); //false
//这里调用了s3,在串池中创建ab
//但是s4是在堆中,s3在串池,不是一个对象,所以是false
String s5="a"+"b";
//javac优化:编译期直接将常量"a"和"b"相加得到ab,不用StringBuilder运行时处理
sout(s3==s5); //true
//这里调用了s3和s5,s3先在串池中查找,存在ab,所以就直接调用
//再调用s5,再在串池中查找,同样找到了,所以是串池的同一个对象
//返回true
原因: s1和s2是变量,在运行时引用的值可能被修改,结果不确定,必须在运行期间处理成ab
而"a","b"是常量,javac在编译期间优化,结果已经在编译期间确定为ab,无法改变
总结: 字符串变量只有在调用时,才会被创建,而且java底层对字符串加法做了优化,使用 StringBuilder操作,最终获取的方式是使用new的方式获取,存放在堆中
字符串常量的计算,会直接在编译时期获得结果,然后直接在串池中寻找
串池: 数据结构: 哈希表=数组+链表
特性: 常量池中字符串只是符号,第一次用到时才会变成对象
利用串池的机制,避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder
字符串常量拼接的原理是编译期优化
jdk8: 可以用intern方法,指定将串池中还没有的字符串对象放入串池,并返回该对象
jdk6: 如果串池中没有,就会复制对象,并放入串池.如果有就不放如串池
串池的位置: 1.6: 串池在常量池中
1.8后: 串池到了堆中
原因: 永久代的内存回收效率低,只有等待老年代的空间不足时才会触发
GCOverheadLimitError: %98的时间用于垃圾回收,但是只回收2%空间,就会抛出异常
垃圾回收: 当内存不足时,没有被引用的字符串常量会被垃圾回收
性能调优: 串池是哈希表结构,哈希表越大,里面buckets桶的个数越多,
哈希碰撞的概率越小,查询速度越快
-XX:StringTable=1009 (1009是串池桶最小值)
考虑将大量且重复字符串对象入池,用intern方法入池,来减少内存占用
包装类的常量池:
包装类的常量池是存在于堆中的,是一种缓存技术,用于存储包装类对象,
如Integer、Long、Character等。
包装类的常量池可以实现包装类对象共享,提高性能。
包装类的常量池只缓存了一定范围内的值,超出范围的值会创建新的对象。