java常量池

原创
2017/04/24 21:19
阅读数 310

首先,我们来看看常量池的概念,常量池可以分成3类:

1.静态常量池:也就是class文件中的常量池,一般用来存放class文件中定义的一些常量,包括类和接口的全限定名,字段的名称和描述符以及方法和名称和描述符。

2.字符串常量池:即class文件中定义的String类型,这个常量池就存在与静态常量池中。

3.运行时常量池:我们平时所说的常量池就是这个了,它存放在方法区中,当所有的class文件都加载完成之后,会将多有的常量都放入其中,以供开发使用。

那么,哪些数据会被存放到常量池中呢?除了上面提到的String类型,8中基本数据类型中有6中都会存放如常量池中,下面我们来看一段代码:

public class ConstantTest {

	public static void main(String[] args) {
		//验证Integer类型
		Integer i1 = 127;
		Integer i2 = 127;
		System.out.println(i1==i2);//true
		Integer i3 = 128;
		Integer i4 = 128;
		System.out.println(i3==i4);//false
		
		//验证Long类型
		Long l1 = 127L;
		Long l2 = 127L;
		System.out.println(l1==l2);//true
		Long l3 = 128L;
		Long l4 = 128L;
		System.out.println(l3==l4);//false
		
		//验证Byte类型
		Byte b1 = 127;
		Byte b2 = 127;
		System.out.println(b1==b2);//true
		
		//验证short类型
		Short s1 = 127;
		Short s2 = 127;
		System.out.println(s1==s2);//true
		Short s3 = 128;
		Short s4 = 128;
		System.out.println(s3==s4);//false
		
		//验证Character类型
		Character c1 = 'a';
		Character c2 = 'a';
		System.out.println(c1==c2);//true
		Character c3 = '你';
		Character c4 = '你';
		System.out.println(c3==c4);//false
		
		//验证boolean类型
		Boolean bo1 = true;
		Boolean bo2 = true;
		System.out.println(bo1==bo2);//true
		
		//验证double类型
		Double d1 = 1.2;
		Double d2 = 1.2;
		System.out.println(d1==d2);//false
		
		//验证float类型
		Float f1 = 1.2f;
		Float f2 = 1.2f;
		System.out.println(f1==f2);//false
	}
}

通过以上的代码和运行结果,我们可以知道,8种数据类型的包装类中,除了Double和Float,其它6种都会进入常量池,但是数值范围在-128到127之间。

下面我们再来看看String类型:

public class ConstantTest {

	public static void main(String[] args) {
		String s1 = "java";
		String s2 = new String("java");
	}
}

这两行代码都是将”java“这个字符串赋值,但是在内存空间的角度来看,它们是有区别的,看下面这张图:创建

图中的两条绿线比较好理解,就是直接赋值的时候,是去常量池中查看是否有该对象,如果没有,就创建并返回其地址引用,如果有,就直接返回地址引用,而通过new的方法创建的String对象是存放在堆内存种的,值得注意的是那条红线,在执行new String的时候,也会去常量池中查看该字符串是否已经存在,如果不存在,那么就在常量池中创建一个。所以如果问new String("java")这句代码产生了几个对象,如果常量池中已经有这个对象了,那么只会产生一个,如果没有,那么会产生两个。

我们再来看看下面这段代码:

public class ConstantTest {

	public static void main(String[] args) {
		String s1 = "hello";
		String s2 = "world";
		String s3 = "hello"+"world";
		String s4 = s1+s2;
		String s5 = "helloworld";
		System.out.println(s3 == s5);//true
		System.out.println(s4 == s5);//false
	}
}

为什么会出现这种结果呢?我们来分析一下,前两行代码执行之后,会向常量池中添加"hello"和 "world"这两个常量,由于jvm的编译时优化,当两个常量进行相加的时候,会将这个组成的新的常量添加到常量池中,并将引用返回,所以第三行代码就是向常量池中添加了"helloworld",并将地址值赋给 s3,所以第一个为true,那么第二个为什么为false呢?因为第四行代码是两个变量相加,变量具有不确定性,所以jvm的编译优化不会起作用,所以第二个是false。

再来说说常量池的位置,在jdk1.6及之前的版本中,常量池位于perm区,也就是我们常说的方法区,这个区和堆是没有关系的,它们是分开的,而到了1.7版本之后,常量池就转放到heap中了,而且值得注意的是,在1.8版本后,perm区也被取消了,取代的是元空间(metaspace)。

下面这段代码是在jdk1.8环境下运行的:

public class InternTest {
	
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		for (int i = 0; i < 500000000; i++) {
			String intern = String.valueOf(i).intern();
			list.add(intern);
		}
	}
}

我们将jvm堆内存的大小设置小一点:

-Xmx1m -Xms1m -XX:-UseGCOverheadLimit

这里注意一定要使用list将数据添加进去,否则会由于GC而不会出现内存溢出,运行结果如下 输入图片说明

发现是堆内存溢出了,说明在1.8的时候,常量池已经被移到了heap中。

到这里,常量池的基本内容就差不多了,不过既然是运行时常量池,就必定牵扯到动态的向其中添加数据,开发中常见的方法就是String的intern方法,想要深入了解这个方法,可参考String的intern方法的深入分析

展开阅读全文
打赏
0
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部