【jvm】字符串常量池问题

目录

        • 一、基本概念
          • [1.1 说明](#1.1 说明)
          • [1.2 特点](#1.2 特点)
        • 二、存放位置
          • [2.1 JDK1.6及以前](#2.1 JDK1.6及以前)
          • [2.2 JDK1.7](#2.2 JDK1.7)
          • [2.3 JDK1.8及以后](#2.3 JDK1.8及以后)
        • 三、工作原理
          • [3.1 创建字符串常量](#3.1 创建字符串常量)
          • [3.2 使用new关键字创建字符串](#3.2 使用new关键字创建字符串)
        • 四、intern()方法
          • [4.1 作用](#4.1 作用)
        • 五、优点
        • 六、字节码分析
          • [6.1 示例1](#6.1 示例1)
            • [6.1.1 代码示例](#6.1.1 代码示例)
            • [6.1.2 字节码](#6.1.2 字节码)
            • [6.1.3 解析](#6.1.3 解析)
          • [6.2 示例2](#6.2 示例2)
            • [6.2.1 代码示例](#6.2.1 代码示例)
            • [6.2.2 分析(jdk8)](#6.2.2 分析(jdk8))
          • [6.3 示例3](#6.3 示例3)
            • [6.3.1 代码示例](#6.3.1 代码示例)
            • [6.2.2 分析(jdk8)](#6.2.2 分析(jdk8))
一、基本概念
1.1 说明
  • 1.JVM字符串常量池是Java虚拟机(JVM)中一个特殊的内存区域。
  • 2.JVM字符串常量池用于存储字符串常量。
  • 3.提高性能和减少内存开销。
  • 4.字符串常量池是JVM用于存储字符串常量的一个内存区域,避免了相同字符串的重复创建,节省内存空间。
1.2 特点
  • 1.字符串常量池中的字符串对象是不可变的。
  • 2.相同的字符串常量在池中只存储一份,通过引用共享。
二、存放位置
2.1 JDK1.6及以前
  • 1.字符串常量池存放在永久代中,永久代是非堆内存的一部分,用于存储类的元数据、常量、静态变量等。
2.2 JDK1.7
  • 1.字符串常量池从永久代移动到了Java堆中,而运行时常量池保留在永久代中。
  • 2.这一变化为了适应永久代内存限制问题,并提升性能。
2.3 JDK1.8及以后
  • 1.永久代被移除,取而代之的是元空间,字符串常量池仍然位于Java堆中。
  • 2.运行时常量池被移动到元空间。
三、工作原理
3.1 创建字符串常量
  • 1.使用双引号创建字符串时(String a = "123"; ),JVM会首先在字符串常量池中查找是否已存在该字符串。
  • 2.如果存在,则直接返回池中该字符串的引用。
  • 3.如果不存在,则在常量池中创建该字符串的实例,并返回其引用。
3.2 使用new关键字创建字符串
  • 1.使用new关键字创建字符串对象(如String str = new String("abc");)时,JVM会在堆内存中创建一个新的字符串对象,而不管字符串常量池中是否已存在相同的字符串。
  • 2.如果需要,可以通过调用intern()方法将新创建的字符串对象放入常量池中。
四、intern()方法
4.1 作用
  • 1.intern()方法是String类的一个本地方法。
  • 2.用于将字符串对象添加到字符串常量池中。
  • 3.如果常量池中已经包含了一个等于此String对象的字符串(使用equals(Object)方法确定),则返回代表池中这个字符串的String对象的引用。
  • 4.否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。
五、优点
  • 1.节省内存:通过共享相同的字符串常量,避免了不必要的重复创建。
  • 2.提高性能:减少了对象创建和垃圾回收的开销。
  • 3.简化字符串比较:由于字符串常量池中的字符串是唯一的,可以使用==操作符来比较字符串的引用,从而简化比较操作。
六、字节码分析
6.1 示例1
6.1.1 代码示例
@Test
public void test(){
    String str1 = new String("hello") + new String("world");
    String str2 = "helloworld";
    System.out.println(str1 == str2);
}
6.1.2 字节码
 0 new #2 <java/lang/StringBuilder>
 3 dup
 4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
 7 new #4 <java/lang/String>
10 dup
11 ldc #5 <hello>
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <world>
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
34 astore_1
35 ldc #10 <helloworld>
37 astore_2
38 getstatic #11 <java/lang/System.out : Ljava/io/PrintStream;>
41 aload_1
42 aload_2
43 if_acmpne 50 (+7)
46 iconst_1
47 goto 51 (+4)
50 iconst_0
51 invokevirtual #12 <java/io/PrintStream.println : (Z)V>
54 return
6.1.3 解析
    1. 0 new #2 <java/lang/StringBuilder>: 调用StringBuilder的new方法
    1. 3 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
    1. 4 invokespecial #3 <java/lang/StringBuilder. : ()V>:执行StringBuilder的初始化方法,会消耗操作数栈顶一个字。
    1. 7 new #4 <java/lang/String>:new一个String对象,对象的引用压入操作数栈。
    1. 10 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
    1. 11 ldc #5 :加载栈顶的一个字,即hello。
    1. 13 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一个string对象的引用和复制的字。
    1. 16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> : append操作,将hello追加进来。
    1. 19 new #4 <java/lang/String> : new一个String对象,对象的引用压入操作数栈。
    1. 22 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
    1. 23 ldc #8 : 加载栈顶的一个字,即world。
    1. 25 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一个string对象的引用和复制的字。
    1. 28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>: append操作,将world追加进来。
    1. 31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> :调用StringBuilder的toString方法
    1. StringBuilder的toString方法中是直接new了一个String对象。
    1. 而String str2 = "helloworld";是常量池的引用。
    1. 因此不是同一个内存地址,所以结果是false。
6.2 示例2
6.2.1 代码示例
	@Test
    public void test(){
        String str1 = new String("hello") + new String("world");
        str1.intern();
        String str2 = "helloworld";
        System.out.println(str1 == str2);
    }
6.2.2 分析(jdk8)
  • 1.str1是直接new了一个对象,执行intern()方法后,将字符串对象添加到字符串常量池中。
  • 2.String str2 = "helloworld";会先在字符串常量池中找是否有,如果有则返回其对象的引用。
  • 3.所以结果是true。
6.3 示例3
6.3.1 代码示例
	@Test
    public void test(){
        String str1 = new String("helloworld") ;
        String str2 = "helloworld";
        String intern = str1.intern();
        System.out.println(str1 == str2);
        System.out.println(str1 == intern);
        System.out.println(str2 == intern);
    }
6.2.2 分析(jdk8)
  • 1.str1在堆上new了一个string对象。
  • 2.str2是将字面量"helloworld"放入字符串常量池中。
  • 3.str1调用intern方法,判断字符串常量池中有没有helloworld,发现有,返回了该字符串常量池的引用即str2。
  • 4.此时str1不等于str2。str2和intern是相等的。
相关推荐
阿伟*rui25 分钟前
jvm入门
jvm
学点东西吧.4 小时前
JVM(五、垃圾回收器)
jvm
请你打开电视看看6 小时前
Jvm知识点
jvm
程序猿进阶7 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
阿龟在奔跑18 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
王佑辉19 小时前
【jvm】方法区常用参数有哪些
jvm
王佑辉19 小时前
【jvm】HotSpot中方法区的演进
jvm
Domain-zhuo19 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
Theodore_10222 天前
7 设计模式原则之合成复用原则
java·开发语言·jvm·设计模式·java-ee·合成复用原则
我是苏苏2 天前
Web开发:ORM框架之使用Freesql的DbFrist封装常见功能
java·前端·jvm