一、常量池中都包含啥
1、常量池是一张表,其中包含多种常量-->从编译时已知的数字、文字;到必运行时解析的方法和字段引用。
2、JVM指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。
举例:
(1)类
package com.fupng3.jvm;
public class Hello {
private String name;
final int AGE=30;
public static void main(String[] args) {
int i=10;
//局部常量值不会在常量池中
final int j=20;
String str="abc";
System.out.println(str);
}
}
(2)类中常量池
Constant pool:
#1 = Methodref #7.#30 // java/lang/Object."<init>":()V
#2 = Fieldref #6.#31 // com/fupng3/jvm/Hello.AGE:I
#3 = String #32 // abc
#4 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #35.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #37 // com/fupng3/jvm/Hello
#7 = Class #38 // java/lang/Object
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 AGE
#11 = Utf8 I
#12 = Utf8 ConstantValue
#13 = Integer 30
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/fupng3/jvm/Hello;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 i
#26 = Utf8 j
#27 = Utf8 str
#28 = Utf8 SourceFile
#29 = Utf8 Hello.java
#30 = NameAndType #14:#15 // "<init>":()V
#31 = NameAndType #10:#11 // AGE:I
#32 = Utf8 abc
#33 = Class #39 // java/lang/System
#34 = NameAndType #40:#41 // out:Ljava/io/PrintStream;
#35 = Class #42 // java/io/PrintStream
#36 = NameAndType #43:#44 // println:(Ljava/lang/String;)V
#37 = Utf8 com/fupng3/jvm/Hello
#38 = Utf8 java/lang/Object
#39 = Utf8 java/lang/System
#40 = Utf8 out
#41 = Utf8 Ljava/io/PrintStream;
#42 = Utf8 java/io/PrintStream
#43 = Utf8 println
#44 = Utf8 (Ljava/lang/String;)V
(3)jvm指令根据常量池找到要执行的类、方法、参数信息
二、运行时常量池中数据的延时加载
1、当类被加载,它的常量池中信息就会被放入运行时常量池,并把里面的符号信息变为真实地址;
2、常量池中信息不是全部一次性加载,而是在执行代码时,如果发现常量池中内容不在运行时常量池中时,再加载(延迟加载);
3、运行时常量池在jdk1.8及之后,是放在堆中的,当常量不被引用时会被gc回收。
举例1:以字符串常量来演示运行时常量池中数据的延迟加载
(1)测试代码
public class Hello {
public static void main(String[] args) {
String str1="a"; //运行该行代码后,运行时常量池中有字符串a
String str2="b"; //运行该行代码后,运行时常量池中有字符串a、b
String str3="ab";//运行该行代码后,运行时常量池中有字符串a、b、ab
String str4=str1+str2; //str4的引用在堆中
String str5="a"+"b"; //编译期优化
System.out.println(str3==str4);
System.out.println(str3==str5);
}
}
(2)输出
false
true
更多案例参考:Java基础--03--String
三、intern方法
intern方法用于将字符串放在运行时常量池中。
1、如果字符串已经在运行时常量池中,则不放入
2、如果字符串不在运行时常量池中,则放入
如上2种情况,都会返回常量池中对象;
运行时常量池中存放的是类中常量池中的信息,动态生成的字符串不在运行时常量池中
举例1:
public static void main(String[] args) {
String str1=new String("a")+new String("b");
String str3=str1.intern();
String str2="ab";
System.out.println(str1==str2);
System.out.println(str3==str2);
}
输出
true
true
举例2:
public static void main(String[] args) {
String str2="ab";
String str1=new String("a")+new String("b");
String str3=str1.intern();
System.out.println(str1==str2);
System.out.println(str3==str2);
}
输出
false
true
举例3:jdk1.6中的intern
jdk1.6与jdk1.8的intern方法处理逻辑不同:
1、如果有则并不会放入,返回运行时常量池中的对象;
2、如果没有,则会复制一份str1放到运行时常量池中,返回运行时常量池中的对象;
//jdk1.6中运行结果
public static void main(String[] args) {
String str1=new String("a")+new String("b");
String str3=str1.intern();
String str2="ab";
System.out.println(str1==str2);
System.out.println(str3==str2);
}
输出:
false
true
public static void main(String[] args) {
String str2="ab";
String str1=new String("a")+new String("b");
String str3=str1.intern();
System.out.println(str1==str2);
System.out.println(str3==str2);
}
//输出结果
false
true
举例3:synchronized (String)的问题
(1)代码示例
public class TestSynString {
public static void main(String[] args) throws InterruptedException {
//多个用户执行多次任务,每个用户执行完一次才能执行下一次
for (int i=1;i<=10;i++){
for (int j=1;j<=10;j++){
final String userId=String.valueOf(j);
new Thread(()->{
try {
synchronized (userId) {
System.out.println("获得锁开始执行,uid="+userId);
Thread.sleep(20); //sleep模拟具体业务执行耗时
}
//执行结束释放锁
System.out.println("释放锁,uid="+userId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
Thread.sleep(60000);
}
}
(2)运行结果
获得锁开始执行,uid=1
获得锁开始执行,uid=2
获得锁开始执行,uid=3
获得锁开始执行,uid=4
获得锁开始执行,uid=5
获得锁开始执行,uid=6
获得锁开始执行,uid=7
获得锁开始执行,uid=8
获得锁开始执行,uid=9
获得锁开始执行,uid=10
获得锁开始执行,uid=1
获得锁开始执行,uid=2
。。。
从执行结果看synchronized (userId)给同一个uid加锁并没有生效,原因是同一个数字对应的userId对象不同,所以需要使用synchronized (userId.intern())
四、运行时常量池长度
运行时常量池是一个固定大小的Hashtable(数组+链表+红黑树【jdk8以后】),长度默认为60013,如果放进的常量非常多,就会造成Hash冲突严重影响性能,可以通过-XX:StringTablesize调整长度优化性能。