GC调优实战及常量池详解

  作者:图灵javaer

jdk自带诊断工具调优


在线预览


第三方诊断工具调优


在线预览


Class常量池与运行时常量池


Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,

还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量(Literal)符号引用(Symbolic 

References)


字面量

字面量就是指由字母、数字等构成的字符串或者数值常量

字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面

  • 1 int a = 1;
  • 2 int b = 2;
  • 3 int c = "abcdefg";
  • 4 int d = "abcdefg";


符号引用

符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:

  • 类和接口的全限定名 
  • 字段的名称和描述符 
  • 方法的名称和描述符


上面的a,b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名,

main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。

这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装

入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也

就是我们说的动态链接了。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的

地址,主要通过对象头里的类型指针去转换直接引用


看一个例子:
1 //字符串常量池:"计算机"和"技术" 堆内存:str1引用的对象"计算机技术"
2 //堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对
象引用
3 String str2 = new StringBuilder("计算机").append("技术").toString(); //没有出现"计算机技术"字面量,所以不会在常量池里生
成"计算机技术"对象
4 System.out.println(str2 == str2.intern()); //true
5 //"计算机技术" 在池中没有,但是在heap中存在,则intern时,会直接返回该heap中的引用
6
7 //字符串常量池:"ja"和"va" 堆内存:str1引用的对象"java"
8 //堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对
象引用
9 String str1 = new StringBuilder("ja").append("va").toString(); //没有出现"java"字面量,所以不会在常量池里生成"java"对象
10 System.out.println(str1 == str1.intern()); //false
11 //java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了
12
13 String s1=new String("test");
14 System.out.println(s1==s1.intern()); //false
15 //"test"作为字面量,放入了池中,而new时s1指向的是heap中新生成的string对象,s1.intern()指向的是"test"字面量之前在池中生成的
字符串对象
16
17 String s2=new StringBuilder("abc").toString();
18 System.out.println(s2==s2.intern()); //false
19 //同上



八种基本类型的包装类和对象池


1 public class Test {
2
3  public static void main(String[] args) {
4  //5种整形的包装类Byte,Short,Integer,Long,Character的对象,
5  //在值小于127时可以使用对象池
6  Integer i1 = 127; //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
7  Integer i2 = 127;
8  System.out.println(i1 == i2);//输出true
9
10  //值大于127时,不会从对象池中取对象
11  Integer i3 = 128;
12  Integer i4 = 128;
13  System.out.println(i3 == i4);//输出false
14
15  //用new关键词新生成对象不会使用对象池
16  Integer i5 = new Integer(127);
17  Integer i6 = new Integer(127);
18  System.out.println(i5 == i6);//输出false
19
20  //Boolean类也实现了对象池技术
21  Boolean bool1 = true;
22  Boolean bool2 = true;
23  System.out.println(bool1 == bool2);//输出true
24
25  //浮点类型的包装类没有实现对象池技术
26  Double d1 = 1.0;
27  Double d2 = 1.0;
28  System.out.println(d1 == d2);//输出false
29  }
30 }

示例:


1 String s0="zhuge";
2 String s1=new String("zhuge");
3 String s2="zhu" + new String("ge");
4 System.out.println( s0==s1 );    // false
5 System.out.println( s0==s2 );   // false
6 System.out.println( s1==s2 );    // false

分析:用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池

中,它们有自己的地址空间。

s0还是常量池 中"zhuge”的引用,s1因为无法在编译期确定,所以是运行时创建的新对象”zhuge”的引用,s2因为有后半部分 new String(”ge”)所以也无法在编译期确定,所以也是一个新创建对象”zhuge”的引用;明白了这些也就知道为何得出此结果了。


补充:String的intern()方法


作用:尽量直接从常量池中拿对象,尽量不new新的String对象




toString()方法会新new出一个String对象,所以s2指向堆中的String对象




而s2.intern()指向常量池中的字面量


相当于如下所示

1 String s2=new String("abc");
2 String s2.intern()="abc";

所以结果:System.out.println(s2 == s2.intern()); //false


相关推荐

评论 抢沙发

表情

分类选择