一个例子

public class TestString{
public static void main(String[] args){
String a = "a";
String b = a+"b";
String c = "ab";
String d = "a" + "b";
System.out.println(c == d); //true!!!!
System.out.println(c == d.intern()); //true
System.out.println(b == c); //false
System.out.println(b.intern() == c); //true!!!
System.out.println(b == c.intern()); //false
System.out.println(b == d); //false
}
}

结果分析:

c==d是true,是因为d="a"+"b"是两个常量String的对象相加,返回的String对象就是常量String;

b==c是false,是因为b=a+“b”,java 重载了“+”,反编译java字节码可以发现“+”其实是调用了StringBuilder 所以使用了“+”其实是生成了一个新的对象;

b.intern() == c 是true, 因为,如果常量池中存在当前字符串,就会直接返回当前字符串;如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回。

后续把上面的猜想挨着验证;

先编译:javac TestString.java;

在使用cfr-0.137.jar将java反编译:java -jar cfr-0.137.jar TestString.class --stringbuilder false ;
反编译结果如下:

/*
* Decompiled with CFR 0.137.
*/
import java.io.PrintStream; public class TestString {
public static void main(String[] arrstring) {
String string = "a";
String string2 = new StringBuilder().append(string).append("b").toString();
String string3 = "ab";
String string4 = "ab";
System.out.println(string3 == string4);
System.out.println(string3 == string4.intern());
System.out.println(string2 == string3);
System.out.println(string2.intern() == string3);
System.out.println(string2 == string3.intern());
System.out.println(string2 == string4);
}
}

可以看到,两个字符串相加,如果其中一个不是常量字符串(即不是通过变量名引用的字符串如"LuoTiany"),那么编译器编译后,JVM执行就会通过new StringBuilder对象操作,如果在一个循环中,就会产生多个StringBuilder对象,所以字符串相加,尽量使用StringBuilder对象的append(异步)或StringBuffer对象的append(同步);

使用javap -c TestString.class反编译结果如下(javap反编译参数):

Compiled from "TestString.java"
public class TestString {
public TestString();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // String a
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String b
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: ldc #8 // String ab
25: astore_3
26: ldc #8 // String ab
28: astore 4
30: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_3
34: aload 4
36: if_acmpne 43
39: iconst_1
40: goto 44
43: iconst_0
44: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
47: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
50: aload_3
51: aload 4
53: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String;
56: if_acmpne 63
59: iconst_1
60: goto 64
63: iconst_0
64: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
67: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
70: aload_2
71: aload_3
72: if_acmpne 79
75: iconst_1
76: goto 80
79: iconst_0
80: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
83: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
86: aload_2
87: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String;
90: aload_3
91: if_acmpne 98
94: iconst_1
95: goto 99
98: iconst_0
99: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
102: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
105: aload_2
106: aload_3
107: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String;
110: if_acmpne 117
113: iconst_1
114: goto 118
117: iconst_0
118: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
121: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
124: aload_2
125: aload 4
127: if_acmpne 134
130: iconst_1
131: goto 135
134: iconst_0
135: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
138: return
}

上面如果看不懂,可以在这里找,javap编译后二进制指令代码详解:http://www.blogjava.net/DLevin/archive/2011/09/13/358497.html#Post

JVM内存中的常量池的位置

@Test
public void constantPool(){
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
}

在执行的时候先设置VM启动参数:

在原来的VM options :-ea 后面追加-Xmx128m -Xms64m -Xmn32m -Xss16m -XX:-UseGCOverheadLimit;

这里的-XX:-UseGCOverheadLimit是关闭GC占用时间过长时报的异常,-Xmx等参数见:https://www.cnblogs.com/theRhyme/p/9120705.html

执行上面的单元测试,等了数十秒:

说明在Java8中,字符串常量池在JVM的堆中。参考:https://blog.csdn.net/u014039577/article/details/50377805

为什么String被设计为不可变?

https://www.cnblogs.com/wuchanming/p/9201103.html

  • 安全首要原因是安全,不仅仅体现在你的应用中,而且在JDK中,Java的类装载机制通过传递的参数(通常是类名)加载类,这些类名在类路径下,想象一下,假设String是可变的,一些人通过自定义类装载机制分分钟黑掉应用。如果没有了安全,Java不会走到今天
  • 性能 string不可变的设计出于性能考虑,当然背后的原理是string pool,当然string pool不可能使string类不可变,不可变的string更好的提高性能。
  • 线程安全当多线程访问时,不可变对象是线程安全的,不需要什么高深的逻辑解释,如果对象不可变,线程也不能改变它。

Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置的更多相关文章

  1. JDK8中String的intern()方法详细解读【内存图解+多种例子+1.1w字长文】

    写在前面,欢迎大家关注小编的微信公众号!!谢谢大家!! 一.前言 String字符串在我们日常开发中最常用的,当然还有他的两个兄弟StringBuilder和StringBuilder.他三个的区别也 ...

  2. Java中String类的方法及说明

    String : 字符串类型 一.      String sc_sub = new String(c,3,2);    //      String sb_copy = new String(sb) ...

  3. 【转载】Java中String类的方法及说明

    转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一.      String sc_ ...

  4. java中String的.trim()方法

    该方法去除两边的空白符 原理: 看看源码实现 public String trim() { int len = value.length; ; char[] val = value; /* avoid ...

  5. java中String的一些方法

    1.public String(char[] c,begin,length). 从字符数组c的下标begin处开始,将长度为length的字符数组转换为字符串. begin与length可以省略,即将 ...

  6. JAVA中String类的方法(函数)总结--JAVA基础

    1.concat()方法,当参数为两字符串时,可实现字符串的连接: package cn.nxl123.www; public class Test { public static void main ...

  7. Java中String的split()方法的一些需要注意的地方

    public String[] split(String regex, int limit) split函数是用于使用特定的切割符(regex)来分隔字符串成一个字符串数组,这里我就不讨论第二个参数( ...

  8. Java中String的split()方法的一些疑问和试验

    http://tjuking.iteye.com/blog/1507855 和我想的还是不大一样,因为不知道源码也不知道具体是怎么实现的,我的理解如下: 当字符串只包含分隔符时,返回数组没有元素:当字 ...

  9. Java中String对象创建机制详解()

    一String 使用 private final char value来实现字符串存储 二Java中String的创建方法四种 三在深入了解String创建机制之前要先了解一个重要概念常量池Const ...

随机推荐

  1. Tomcat启动报错:Possible root causes include a too low setting for -Xss and illegal cyclic inheritance dependencies

    错误代码如下: Caused by: java.lang.IllegalStateException: Unable to complete the scan for annotations for ...

  2. redis的哨兵集群,redis-cluster

    #主从同步redis主从优先1.保证数据安全,主从机器两份数据一主多从2.读写分离,缓解主库压力主redis,可读可写slave身份,只读   缺点1.手动主从切换假如主库挂了,得手动切换master ...

  3. lunix 项目部署 *****

    linux基本管理命令 服务器上安装服务,python3.6(宿主机上的物理解释器)1.虚拟解释器virtualenv虚拟出多个干净.隔离的python解释器环境问题:管理上较为麻烦,需要找到venv ...

  4. 数据库数据以Excel的方式导出

    import java.io.Serializable; import java.util.List; import com.cfets.cwap.s.util.db.TableColumn; /** ...

  5. python学习之----深网和暗网

    深网是网络的一部分,与浅网(surface Web)对立.浅网是互联网上搜索引擎可以抓 到的那部分网络.据不完全统计,互联网中其实约90% 的网络都是深网.因为谷歌不 能做像表单提交这类事情,也找不到 ...

  6. 零基础学习python_文件(28-30课)

    本人小白一枚,随着现在对测试要求越来越高,动不动就要去会一门编程语言,没办法只能学习学习Python,今天看到几个月前还是菜鸟的人突然就已经能使用Python写简单系统了,没办法,虽然之前也简单学习过 ...

  7. [java,2018-01-16] HttpClient发送、接收 json 请求

    最近需要用到许多在后台发送http请求的功能,可能需要发送json和xml类型的数据. 就抽取出来写了一个帮助类: 首先判断发送的数据类型是json还是xml: import org.dom4j.Do ...

  8. Java并发编程:Java Thread 的 run() 与 start() 的区别

    1. sleep 和 wait 方法解释 sleep()方法是Thread类里面的,主要的意义就是让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后 ...

  9. 6.简单提取小红书app数据(简单初步试采集与分析)-1

    采集小红书数据爬虫:1.本来是要通过app端的接口去直接采集数据,但是app接口手机端设置本地代理这边开启抓包后就不能正常访问数据.所以就采用了微信小程序里的小红书app接口去采集数据. 2.通过 f ...

  10. 14.json文件读取

    json文件读取 1.#读取json import json str='''[ { "name":"Tom", "gender":" ...