1. 字符串生成过程

我们都知道String s = "hello java";会将“hello java”放入字符串常量池,但是从jvm的角度来看字符串和三个常量池有关,class常量池,运行时常量池,全局字符串常量池(也就是常说的字符串常量池)

第一个是class的常量池,看一下下面这个代码

public class StringTest {
public void test1() {
String s = "hello java";
}
}

如果用javap -v StringTest.class 来查看他的字节码文件,代码如下

Constant pool:
#1 = Methodref #4.#17 // java/lang/Object."<init>":()V
#2 = String #18 // hello java
...
#18 = Utf8 hello java

#2表示有一个字符串的索引指向#18,一个utf8编码的字符串字面量,这个#18只代表由utf编码的数据,不是java对象,#2是java对象,但是他现在还没有初始化,#18是在文件编译后就生成的

utf8字面量字符串,他在项目启动加载类时就进入了运行时常量池。

那么就有一个问题,#2什么时候初始化,以及什么时候进入全局字符串常量池(也就是平常说的字符串常量池)呢?我们继续看字节码

源码:
public void test1() {
String s = "hello java";
} 字节码:
public void test1();
0: ldc #2 // String hello java
2: astore_1
3: return
为了方便看,去掉部分代码
我们看字节码第一行,很明显的看到他调用了#2,ldc的作用是将常量池中的数据加载到操作数栈中(简单来说就是进行数据操作的地方),这个时候#2肯定要初始化生成java对象了。
如果是java7及以后的版本这时候jvm会在堆中创建一个“hello java”的对象,。然后将这个对象的引用放入全局字符串变量池(也是在堆中)中,当以后出现“hello java”,能在全局字符串变量池找到,就不会再生成对象。
如果是java6版本jvm会在permGen中创建一个“hello java”的对象,。然后将这个对象的引用放入全局字符串变量池(在permGen)中,当以后出现“hello java”,能在全局字符串变量池找到,就不会再生成对象。

所以全局字符串常量池中存放的只是索引,他类似于java中的HashMap,key是字面量字符串,value是是指向真正字符串对象的引用。

2.String.intern

JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中然后把这个字符串的引用放入全局字符串常量池,返回的也是永久代中这个字符串的实例的引用。

JDK1.7中,intern()方法首次遇到字符串实例时不会在复制实例,直接把这个实例的引用存入全局字符串常量池,返回的就是这个字符串实例的引用。

那就有一个疑问,为什么jdk1.6要重新复制一份呢? 因为1.6时字符串常量池在永久代,而通过new 产生的字符串在堆中,两个区域内存隔离,永久代无法存堆中的引用,

1.7时代,jvm把字符串常量池移到了堆中,所以在1.7中就不用创造实例了。

/**
* jdk 1.8
*/
public static void main(String[] args) {
//调用了new String
String s1 = new StringBuilder("lh").append("cy").toString();
String s2 = s1.intern();
//true
System.out.println(s1 == s2);
}

3.字符串相加

3.1编译时确定的字符串相加

//源代码
public static void main(String[] args) {
String s = "lh" + "cy";
}
//字节码类的常量池
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = String #21 // lhcy
#21 = Utf8 lhcy
//字节码操作指令
0: ldc #2 // String lhcy
2: astore_1
3: return

可以看见 “lh”和“cy”被拼成了“lhcy”,这个结论大家应该早就知道,这里从字节码角度来看一下。

3.2运行时确定的字符串相加

源代码:
public String test(String s1, String s2) {
return s1 + s2;
} 字节码:
//新建一个StringBuilder对象
0: new #2 // class java/lang/StringBuilder
//复制新建的StringBuilder对象
3: dup
//消耗刚才复制的StringBuilder对象用于初始化
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
//加载s1
7: aload_1
//拼接到StringBuilder
8: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
加载s2
11: aload_2
//拼接到StringBuilder
12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//调用StringBuilder的toString方法
15: invokevirtual #5 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
18: areturn

根据注释应该知道了上述代码创建了一个StringBuilder然后append,最后toString。那么又有一个小疑问,既然两个字符串相加生成了StringBuilder,那么我们还

手动创建StringBuilder干嘛,为什么不让我们之间使用“+”来拼接字符串。那么请看下面的代码

//源代码
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 100; i++) {
s += i;
}
}
//jvm实际执行的代码
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 100; i++) {
StringBuilder stringBuilder = new StringBuilder();
s = stringBuilder.append(s).append(i).toString();
}
}

从代码块中,我们发现当每次要给字符串赋值时,StringBuilder就会调用toString来新建字符串,jvm并不知道你只需要循环后的结果,在其中创建了大量无用的String对象,不仅耗时

创建了对象,并且占用了大量内存,从而加快了gc的频率,对系统运行非常不利。

总结

String是一个常用的类,基本使用非常简单,但是他的底层实现非常复杂,c++基础不错的同学可以去看一下String.intern()的源码和ldc的源码。

从JVM的角度解析String的更多相关文章

  1. 从字节码和JVM的角度解析Java核心类String的不可变特性

    1. 前言 最近看到几个有趣的关于Java核心类String的问题. String类是如何实现其不可变的特性的,设计成不可变的好处在哪里. 为什么不推荐使用+号的方式去形成新的字符串,推荐使用Stri ...

  2. 从jvm的角度来看单例模式

    最近在看jvm,发现随着自己对jvm底层的了解,现在对java代码可以说是有了全新的认识.今天就从jvm的角度来看一看以前自以为很了解的单例模式. 了解单例模式的人都知道,单例模式有两种:" ...

  3. 深入解析String#intern

    转自:https://tech.meituan.com/in_depth_understanding_string_intern.html 深入解析String#intern john_yang ·2 ...

  4. JVM内存区域解析

    引言 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间.有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的 ...

  5. 从JVM的角度看JAVA代码--代码优化

    从JVM的角度看JAVA代码–代码优化 从JVM的角度看JAVA代码代码优化 片段一反复计算 片段二反复比較 在JVM载入优化为class文件,运行class文件时,会有JIT(Just-In-Tim ...

  6. (转载)深入解析String#intern

    本文转载自:深入解析String#intern 引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念. ...

  7. 模仿GsonConverter 写的StringConverter 解析String字符串

    使用自己写的StringConverter 来封装的 Converter 来解析String private static final RestAdapter CAMERA_CLIENT_NETWOR ...

  8. String学习之-深入解析String#intern

    引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念.常量池就类似一个JAVA系统级别提供的缓存. 8 ...

  9. JAVA代码解析String字符串(json格式的)

    java解析String字符串(json格式) 需要jar包:json-lib-2.4-jdk15.jar 一. String str = "{\"name\":\&qu ...

随机推荐

  1. tomcat深入学习—权限篇

    如果想给一个应用程序配置权限,可能需要的代码量很多,但你知道吗,仅通过配置tomcat,就能有同样的效果(为一个web应用添加了权限) 效果:打开http://localhost:8080/searc ...

  2. 详细Http状态查询

    状态代码 状态信息 含义 100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分.(HTTP 1.1新) 101 Switching Protocols 服务器将遵从客户的请求转 ...

  3. Node.Js安装教程

    Node.Js安装教程 介绍下我的环境 环境 值 操作系统 win10 64bit Node.Js 8.9.4 emmmm 表格中毒了,为什么出不来效果 一.下载及安装 这个可以去Node.Js官网上 ...

  4. Apache HTTP Server——官网下载

    Windows版 Apache 2.4.x OpenSSL 1.0.2 VC14  ——Apache 2.4.34 x64(注:x64就是64位,x86就是32位) https://www.apach ...

  5. var与this定义变量的区别以及疑惑

    我们知道: var可以定义一个局部变量,当然如果var定义在最外层的话,就是全局的局部变量,也就算是全局变量了. 而this关键字定义的变量准确的说应该算是成员变量.即定义的是调用对象的成员变量. 另 ...

  6. quartz简单应用

    pom.xml<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3 ...

  7. 【Python】unicode' object is not callable

    在Python中,出现'unicode' object is not callable的错误一般是把字符串当做函数使用了.

  8. 【scala】匿名函数和闭包

    函数的类型和值 Scala是一种纯面向对象的语言,每个值都是对象.Java是一种不全面向对象的语言. Scala也是一种函数式语言,其函数也能当成值来使用.Java则是指令试编程. 但是Scala同时 ...

  9. null与""的区别

    两者的区别与 “数字0和没有不是同一种概念”是一个道理.null是空对象,""是空字符串null可以赋值给任何对象,而""就不行了,只能赋值给字符串对象如:St ...

  10. Mac os x 下配置Intellij IDEA + Tomcat 出现权限问题的解决办法

    出现的错误提示如下: 下午9:11:27 All files are up-to-date下午9:11:27 All files are up-to-date下午9:11:27 Error runni ...