JVM字符串常量池StringTable的基本概述

本文详细介绍了Java中的String类,包括其不可变性、内存分配方式以及基本操作。String对象在JDK8之前使用char数组存储,JDK9改为byte数组以节省空间。字符串常量池在不同JDK版本中位置有所变化,影响性能。此外,文章还探讨了String对象在内存中的创建和赋值,以及如何通过intern()方法利用常量池。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


1. String的基本特性

  • String是用一对 “” 引起来表示的字符串;

    • String s1 = “hello”; //字面量的定义方式;
    • String s2 = new String(“hello”);
  • String是被声明为final的,不可被继承;

  • String实现了Serializable接口:表示字符串是支持序列化的;实现了Comparable接口:表示String可以比较大小的;

  • String类在JDK8中的实现将字符存储在 char型 数组中,每个字符使用两个字节(十六位)。从许多不同的应用程序收集的数据表明,字符串是堆使用的主要组成部分,而且,大多数String对象仅包含Latin-1字符。这样的字符只需要一个字节的存储空间,因此char这些String对象的内部数组中的 一半空间都没有使用,所以String在JDK8及以前内部定义了final char[] value用于存储字符串数据;但在JDK9时改为byte[];这样子可以提高String类和相关类的空间效率,同时在大多数情况下保持性能,并保留所有相关Java和本机接口的完全兼容性;同时,非拉丁字符可以用编码进行标识,比如中文可以标识为UTF-8;字符串相关的类,如AbstractStringBuilder,StringBuilder和StringBuffer将更新为使用相同的表示;

public final class String implements java.io.Serializable, Comparable<String>,CharSequence {
@Stable
private final byte[] value;
}
  • String代表不可变的字符序列,简称为不可变性;

    • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值;
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值;
    • 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值;
  • 通过字面量的方式(区别于new)给一个字符串进行赋值,此时的字符串值声明在字符串常量池之中;

package stringtable;

import org.junit.Test;

public class StringTest1 {
    @Test
    public void test1() {
        String s1 = "abc";  //字面量定义的方式,"abc"存储在字符串常量池中
        String s2 = "abc";
        s1 = "hello";

        System.out.println(s1 == s2);   //判断地址:当s1是"abc"时:true  --> 由于s1被改为了"hello",它所引用的地址发生了变化,所以为false

        System.out.println(s1); //hello
        System.out.println(s2); //abc

    }

    @Test
    public void test2() {
        String s1 = "abc";
        String s2 = "abc";
        s2 += "def";
        System.out.println(s2); //abcdef
        System.out.println(s1); //abc
    }

    @Test
    public void test3() {
        String s1 = "abc";
        String s2 = s1.replace('a', 'm');
        System.out.println(s1); //abc
        System.out.println(s2); //mbc
    }
}

  • 字符串常量池中是不会存储相同内容的字符串的

    • String的String Pool 是一个固定大小的Hashtable,默认值大小长度是1009;如果放进StringPool的String非常多, 就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern()时性能会大幅下降;
    • 使用-XX:StringTableSize可设置StringTable的长度;
    • 在JDK6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快;对StringTableSize的设置没有要求;
    • 在JDK7中,StringTable的长度默认值是60013,JDK8开始,1009是StringTable长度可设置的最小值;

2. String的内存分配

  • 在Java语言中有8种基本数据类型和一种比较特殊的类型String;这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念;

  • 常量池就类似一个Java系统级别提供的缓存;8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊;它的主要使用方法有两种;

    • 直接使用双引号声明出来的String对象会直接存储在常量池中,比如String info = “abc”;
    • 如果不是用双引号声明的String对象,可以使用String提供的intern()方法;这个后面重点谈;
    • Java 6及以前,字符串常量池存放在永久代;Java 7中Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内;所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了;字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用String.intern(); Java 8元空间,但是字符串常量仍然在堆中;
    • StringTable为什么要调整:永久代PermSize默认比较小;永久代的垃圾回收频率低;

3. String的基本操作

在这里插入图片描述

class Memory {
    public static void main(String[] args) {//line 1
        int i = 1;//line 2
        Object obj = new Object();//line 3
        Memory mem = new Memory();//line 4
        mem.foo(obj);//line 5
    }//line 9

    private void foo(Object param) {//line 6
        String str = param.toString();//line 7
        System.out.println(str);
    }//line 8
}

在这里插入图片描述

### JVM 字符串常量池工作机制与存储方式 #### 1. 基本概念 字符串常量池是一种特殊的运行时常量池,用于优化字符串的存储和比较。它通过减少重复字符串对象的数量来节省内存并提高性能[^3]。 #### 2. 创建过程 当程序中定义了一个字符串字面量时,JVM 首先会在字符串常量池中查找是否存在相同的字符串实例。如果找到,则直接返回该实例的引用;如果没有找到,则会创建一个新的字符串对象,并将其放入字符串常量池中。 #### 3. 存储位置的变化 在 Java 7 及之前版本中,字符串常量池位于永久代(Permanent Generation),而在 Java 8 中为了改进内存管理和解决永久代溢出问题,字符串常量池被迁移到堆中的元空间(Metaspace)[^2]。 #### 4. 动态添加到字符串常量池 除了静态初始化外,还可以通过 `String.intern()` 方法将任意字符串对象手动加入字符串常量池。此方法允许开发者显式控制哪些字符串应该进入常量池[^1]。 #### 5. 初始状态分析 即使应用程序尚未执行任何自定义逻辑,`StringTable` 中可能已包含大量条目。这是因为类加载过程中涉及的各种名称(如类名、字段名、方法签名等)均作为字符串常量存入其中[^4]。 #### 示例代码展示如何利用intern()函数实现共享相同内容的不同变量指向同一个地址: ```java public class TestIntern { public static void main(String[] args){ String s1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(s1.intern() == s1); // true String s2 = new StringBuilder("ja").append("va").toString(); System.out.println(s2.intern() == s2); // false, because "java" already exists in the pool. } } ``` 上述例子说明了对于新构建的对象调用 intern 后再做相等判断的结果差异取决于目标值是否预先存在于池内。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值