JVM中字符串常量池的详细剖析

引言

推荐阅读:JVM高级特性与最佳实战(一)————JAVA内存区域

你可知道 String s1 = new String(“ddd”);内存中究竟调用了什么?你可知道String s1 = “ab” + “cd”;
String s2 = “abcd”;
System.out.println(s1 == s2);究竟是对是错?

昨天学习了《深入理解JAVA虚拟机》后,对其中一个说法很是在意,
“在目前已经发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出。”
那么什么叫永久代?为什么要从永久代移出字符串常量池?移出来后有什么影响?

首先我们来看看官网翻译后的中文说明:
在JDK 7中,在Java堆的永久生成中不再分配interned字符串,而是在Java堆的主要部分(称为young和old generation)中分配,以及应用程序创建的其他对象。此更改将导致更多的数据驻留在主Java堆中,而在永久生成中数据更少,因此可能需要调整堆大小。由于这种变化,大多数应用程序在堆使用上只会看到相对较小的差异,但是更大的应用程序加载了许多类,或者大量使用了string . intern()方法将看到更显著的差异。

永久代的概念

方法区:是JVM的一种规范,存放类信息、常量、静态变量、即时编译器编译后的代码等;
永久代:是HotSpot的一种具体实现,实际指的就是方法区中的某一块区域,

JDK7之前,字符串常量池被存储在永久代(默认大小是4m)中,因此导致性能问题和OOM;实际上1.7时,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap;
JDK8后,永久代被移到与一个堆不相连的本地内存区域,也即就是元空间
由于类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。
元空间虚拟机采用了组块分配的形式,会导致内存碎片存在。

字符串常量池位置调整对intern方法有什么影响

JDK7之前:
当使用intern方法时,查询字符串常量池是否存在当前字符串,如不存在则将当前字符串复制到字符串常量池中,并返回字符串常量池的引用。
JDK7之后:
当使用intern方法时,查询字符串常量池是否存在当前字符串,如不存在则再从堆中查询,然后存储并返回相关引用。

那么intern方法怎么定义的呢?

根据源码中的注解:
intern的方法返回字符串对象的规范表示形式。其中它做的事情是:首先去判断该字符串是否在常量池中存在,如果存在返回常量池中的字符串(对应一个地址),如果在常量池中不存在,先在常量池中添加该字符串,然后返回在堆中的引用地址。

jdk1.6和jdk1.7中该方法的功能是一致的,不同的是常量池位置的改变(jdk1.7将常量池放在了堆空间中),下面具体说明。

代码探究

package JAVA原理及应用;

/**
 * Author:haozhixin
 * Func:  字符串的常量池 及intern方法探究
 * Date:  20190815
 */
public class InternTest {
	public static void main(String []args){
/*		String s1 = new String("ddd");//在堆中分配了一块内存,存储ddd,并将s1指向该地址,同时检查常量池中没有ddd,则分配一块地址生成ddd。
		String s2 = new String("ddd");//在堆中分配了一块内存,存储ddd,并将s2指向该地址,同时检查常量池中有ddd,则不再生成。
		System.out.println(s1 == s2);//false,因为是两个不同的引用*/

/*		String s1 = "ddd";//字符串直接保存到字符串常量区中,并将s1指向ddd的地址。
		String s2 = "ddd";//在常量区中发现是同一个字符串,将ddd的地址指向s2
		System.out.println(s1 == s2);//true,因为s1  s2指向了同一个地址*/

/*		String s1 = "ab" + "cd";//在字符串常量区中分别有三个字符串  ab  cd  abcd   并且abcd指向了
		String s2 = "abcd";//在字符串常量区中查找abcd,刚好找到,将abcd的地址指向s2
		System.out.println(s1 == s2);//true,指向了同一块地址*/

/*		String temp = "hh";//在字符串常量区中保存hh temp指向hh的地址
		String s1 = "a" + temp;//在字符串a与引用地址相加的时候,会在堆中分配地址,保存ahh,并s1指向堆内存的地址
		String s2 = "ahh";//在字符串常量区中保存ahh s2指向hh的地址
		System.out.println(s1 == s2);//false指向的地址不同*/

		/*String s1 = new String("aaa");//在堆内存中分配地址,创建s1对象,将s1对象的引用保存到栈中。并且拷贝一份到字符串常量池中,但是引用指向堆内存中的aaa
		String s2 = "aaa";//字符串直接保存到字符串常量池中,这个地址与s1的地址肯定是不一样的。
		System.out.println(s1 == s2);//false*/


	/*	String s1 = "ddd";//字符串直接保存到字符串常量区中,并将s1指向ddd的地址。
		String s2 = new String("ddd").intern();//在堆中分配地址,保存ddd,当调用.intern时,会检查字符串常量区中有没有ddd,发现刚好有,便将s2指向到字符串常量区的地址
		System.out.println(s1 == s2);//true,指向了通一块地址。*/


/*		String s1 = new String("bbb").intern();//在堆内存中分配地址,保存bbb。.intern方法则拷贝了bbb到字符串常量区,并将s1指向到字符串常量区的ddd的地址。
		String s2 = "bbb";//指向字符串常量区的bbb地址
		System.out.println(s1 == s2);//true*/

/*
		String s1 = new String("ddd").intern();//在堆中分配地址,保存ddd。.intern方法拷贝了ddd到字符串的常量区,并将s1指向到字符串常量区的ddd的地址
		String s2 = new String("ddd").intern();//在堆中分配地址,保存ddd。.intern方法检查字符串常量区有ddd,所以直接将s2指向到字符串常量区的ddd的地址
		System.out.println(s1 == s2);//true*/

/*		String s1 = new String("ddd");//在堆中分配地址,保存ddd。并将s1指向到堆中的地址,并且拷贝一份到字符串常量池中。
		String s2 = new String("ddd").intern();//在堆中分配地址,保存ddd。.intern方法拷贝了ddd到字符串的常量区,并将s2指向到字符串常量区的ddd的地址
		System.out.println(s1 == s2);//false*/

/*		String s3 = new String("2") + new String("2");//此时生成了四个对象 常量池中的“2”+2个堆中的“2”+堆中的“22”  s3 指向了堆中的22
		String s4 = "22";//字符串常量池中保存22
		s3.intern();//先在s3中查找“22”,刚好有则直接指向字符串常量池中的"22"。
		System.out.println(s3 == s4);//false,指向的引用不同*/
/*
		String s3 = new String("1") + new String("1");  //此时生成了四个对象 常量池中的"1" + 2个堆中的"1" + s3指向的堆中的对象“11”(注此时常量池不会生成"11")
		s3.intern();   //jdk1.7之后,常量池不仅仅可以存储对象,还可以存储对象的引用,会直接将s3的地址存储在常量池
		String s4 = "11";//指向字符串常量区的11地址
		System.out.println(s3 == s4);//true*/
	}
}

总结

  • 字符串的创建方式
  1. 显示调用String的intern方法的时候;
  2. 直接声明字符串字面常量的时候,例如: String a = “aaa”;
  3. 字符串直接常量相加的时候,例如: String c = “aa” + “bb”;
  • 上述方法中,最重要的是intern相关的使用,比如最后一个例子
	String s3 = new String("1") + new String("1"); 
	s3.intern();   
	String s4 = "11";
	System.out.println(s3 == s4);

如果在jdk1.7以前,我们分析下:s3.intern()将在字符串常量区中查找11,发现没有,直接创建11,但是存储的引用是堆中的引用。而常量池中将拷贝一份11存储,并保存了常量池的引用。这两个引用是不一样的,所以是false。
如果在jdk1.8以后,我们分析下:s3.intern()将在字符串常量区中查找11,发现没有,则去堆中寻找,刚好找到,存储11并且返回堆中的引用,所以此时s3和s4的引用都是堆中的引用!

希望大家好好揣摩案例,仔细阅读博客内容!


作者:select you from me
来源:CSDN
转载请联系作者获得授权并注明出处。

你可能感兴趣的:(JAVA原理及应用)