java字符串常量池、class常量池、运行时常量池

1.字符串常量池(String Constant Pool):

1.1:字符串常量池在Java内存区域的哪个位置?

  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
  • 在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。

1.2:字符串常量池是什么?

  • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
  • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
  • 在JDK7.0中,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666

1.3:字符串常量池里放的是什么?

  • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
  • 在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。关于String在内存中的存储和String#intern()方法的说明,可以参考我的另外一篇博客:

需要说明的是:字符串常量池中的字符串只存在一份! 
如:

String s1 = "hello,world!";
String s2 = "hello,world!";

即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。


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
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

首先说明一点,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,
并不是比较内容,比较内容请用String.equals()。

     s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,
说白话点,就是直接把字符串写死,在编译期间,
这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,
s1、s2指向的是同一个内存地址,所以相等。

     s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,
但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,
编译器直接帮你拼好,因此String s3 = "Hel" + "lo";
在class文件中被优化成String s3 = "Hello";,所以s1 == s3成立。

     s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,
是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,
结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。
配上一张简图理清思路:

java字符串常量池、class常量池、运行时常量池_第1张图片

 

s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,
但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,
不可能当解释器用,所以不做优化,等到运行时,s7、s8拼接成的新字符串,
在堆中地址不确定,不可能与方法区常量池中的s1地址相同。

java字符串常量池、class常量池、运行时常量池_第2张图片

 s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。

 s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,
intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,
因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;
而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

至此,我们可以得出三个非常重要的结论:

必须要关注编译期的行为,才能更好的理解常量池。

运行时常量池中的常量,基本来源于各个class文件中的常量池。

程序运行时,除非手动向常量池中添加常量(比如调用intern方法),
否则jvm不会自动添加常量到常量池。

     以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池等等,
但都大同小异,只不过数值类型的常量池不可以手动添加常量,
程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,
只有这个范围的数字可以用到常量池。

2.class常量池(Class Constant Pool):

2.1:class常量池简介:

  • 我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
  • 每个class文件都有一个class常量池。

2.2:什么是字面量和符号引用:

  • 字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
  • 符号引用包括:
  • 1.类和方法的全限定名 :


我的一个类里面引入了:我的一个类里面引入了:

import org.apache.commons.lang3.StringUtils;
import org.ap

这个时候我的类用StringUtils类里面的方法就可以:

boolean isEmpty = StringUtils.isNotEmpty(str);

没错,就是可以直接用他里面的方法。

然后假设这时候我想在这个类里用spring提供的StringUtils里面的方法,
但是我并没有import spring的StringUtils类进来,这时候怎么办呢? 
我们可以这样(前提是有spring的jar):

boolean isEmpty = org.springframework.util.StringUtils.isEmpty(str);

没错 ,就是全限定名访问spring的StringUtils类。

2.字段的名称和描述符 :

Java描述符(修饰符)的类型

以下内容引用自http://wiki.jikexueyuan.com/project/java/modifier-types.html:

描述符(修饰符)是添加到那些定义中来改变他们的意思的关键词。Java语言有很多描述符,包括以下这些:

(1):可访问描述符

(2):不可访问描述符

应用描述符,可以在类、方法、变量中加入相应关键字。描述符要先于声明,如下面的例子所示:

public class className {
   // ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
   // body of method
}

一、可访问描述符

Java提供一系列可访问描述符来设定类,变量,方法和构造器的访问级别。四种访问级别如下:

默认的,对封装可见。不需要描述符。

仅对类可见(private)

全部可见(public)

对封装和子类可见(protected)

二、不可访问描述符

Java提供一些不可访问描述符来满足其他功能。

static描述符是用来创造类方法和变量的。

final描述符用来最终确定和实施类、方法和变量的。

abstract描述符用来创造不允许实例化的类和方法。

synchronized和volatile描述符用来当做线程的。
  • 3.方法的名称和描述符。(和2中差不多的意思)

3.运行时常量池(Runtime Constant Pool):

  • 运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用
  • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

你可能感兴趣的:(jvm)