美文网首页
【String类】对象内存分配详解

【String类】对象内存分配详解

作者: 静筱 | 来源:发表于2019-01-04 16:18 被阅读0次

关于内存分区参见:
Java内存详解 - 内存分区

本文重点描述String对象创建时的内存分配方式。

字符串常量池概述

Java运行时会维护一个String� Pool,也叫“字符串缓存区”。 String Pool用来保存运行时产生的各种字符串,并且池中的字符串内容不重复。

String Pool的存在主要为了同样的字符串内容只保存一份,同样内容的字符串创建新对象时,只需要创建一个新的引用即可。

需要注意的是:jdk1.7 之前 hotspot JVM中常量池位于方法区,而jdk1.7之后,常量池从方法区移除,移到了堆中。

创建String对象的方式

String a = "test";

public class ReferenceTest {
    public static void main(String[] args){
        String a ="test";
    }
}

用javap -v 反编译

public class ReferenceTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = String             #14            // test
   #3 = Class              #15            // ReferenceTest
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               ReferenceTest.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               test     //常量池中保存了test字符串
  #15 = Utf8               ReferenceTest
  #16 = Utf8               java/lang/Object
{
  public ReferenceTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2   // 加载常量"text"到操作数栈
         2: astore_1           // 将操作数栈顶数值存储到局部变量表中的变量
         3: return
      LineNumberTable:
        line 5: 0
        line 7: 3
}

说明

只在常量池中新建了“test”字符串,没有在堆中新建对象,因为没有字节码指令:new

String b = new String("test")

public class ReferenceTest {

    public static void main(String[] args){

        String a = "test";

javap -v ReferenceTest.class


public class ReferenceTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = String             #16            // test
   #3 = Class              #17            // java/lang/String
   #4 = Methodref          #3.#18         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #19            // ReferenceTest
   #6 = Class              #20            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               ReferenceTest.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8               test
  #17 = Utf8               java/lang/String
  #18 = NameAndType        #7:#21         // "<init>":(Ljava/lang/String;)V
  #19 = Utf8               ReferenceTest
  #20 = Utf8               java/lang/Object
  #21 = Utf8               (Ljava/lang/String;)V
{
  public ReferenceTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #2                     // 加载常量"text"到操作数栈
         2: astore_1           // 将操作数栈顶数值存储到局部变量表中的变量a
         3: new           #3                  // 新建String类实例,即在堆内为该实例分配内存空间,并将地址压入操作数栈顶;
         6: dup  //复制操作数栈顶值,并将其压入栈顶(也就是说目前操作数栈上有两个相同的指向堆的地址)
         7: ldc           #2           // 加载常量池内字符串"test"到操作数栈顶
         9: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V 调用String实例的初始化方法new String(String input), 弹出操作数栈顶的2个值,分别为常量字符串值“test”,和一个新实例对应的堆地址,进行赋值,将堆地址指向常量字符串地址        
        12: astore_2 //将操作数栈顶数值(堆上新实例的内存地址)存储到局部变量表中的变量b
        13: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 8: 13
}

说明
因为“test”字符串在常量池中只存在一份,所以new String("test")这里只在堆上新建一个对象,引用常量池中己有的字符串值。

那么如果这个字符串之前在常量池中不存在,又将如何分配内存呢?我们看下一个例子:

String b = new String("test1")

public class ReferenceTest {

    public static void main(String[] args){

        String a = "test";
        String b = new String("test1");

    }
}

javap -v ReferenceTest.class

public class ReferenceTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#16         // java/lang/Object."<init>":()V
   #2 = String             #17            // test
   #3 = Class              #18            // java/lang/String
   #4 = String             #19            // test1
   #5 = Methodref          #3.#20         // java/lang/String."<init>":(Ljava/lang/String;)V
   #6 = Class              #21            // ReferenceTest
   #7 = Class              #22            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               SourceFile
  #15 = Utf8               ReferenceTest.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = Utf8               test
  #18 = Utf8               java/lang/String
  #19 = Utf8               test1
  #20 = NameAndType        #8:#23         // "<init>":(Ljava/lang/String;)V
  #21 = Utf8               ReferenceTest
  #22 = Utf8               java/lang/Object
  #23 = Utf8               (Ljava/lang/String;)V
{
  public ReferenceTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #2                  // String test
         2: astore_1
         3: new           #3                  // class java/lang/String
         6: dup
         7: ldc           #4                  // String test1
         9: invokespecial #5                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        12: astore_2
        13: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 8: 13
}

说明
此例与上例不同之处仅在于常量池中分别保存了"test"和"test1";
说明在new String("test1")操作时,jvm先去常量池中找"test1"是否存在,如果不存在,则先在常量池中创建,再在堆中创建对象,引用该常量池中的字符串,并返回堆中新对象的内存地址。

String c = new String("test" ) + b;

public class ReferenceTest {

    public static void main(String[] args){

        
        String b = new String("test");


        String c = new String("test" ) + b;

    }
}

javap -v ReferenceTest.class

Constant pool:
   #1 = Methodref          #10.#19        // java/lang/Object."<init>":()V
   #2 = Class              #20            // java/lang/String
   #3 = String             #21            // test
   #4 = Methodref          #2.#22         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #23            // java/lang/StringBuilder
   #6 = Methodref          #5.#19         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#24         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#25         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #26            // ReferenceTest
  #10 = Class              #27            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               main
  #16 = Utf8               ([Ljava/lang/String;)V
  #17 = Utf8               SourceFile
  #18 = Utf8               ReferenceTest.java
  #19 = NameAndType        #11:#12        // "<init>":()V
  #20 = Utf8               java/lang/String
  #21 = Utf8               test
  #22 = NameAndType        #11:#28        // "<init>":(Ljava/lang/String;)V
  #23 = Utf8               java/lang/StringBuilder
  #24 = NameAndType        #29:#30        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #25 = NameAndType        #31:#32        // toString:()Ljava/lang/String;
  #26 = Utf8               ReferenceTest
  #27 = Utf8               java/lang/Object
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = Utf8               append
  #30 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #31 = Utf8               toString
  #32 = Utf8               ()Ljava/lang/String;
{
  public ReferenceTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: new           #2                  // 新建String类实例,即在堆内为该实例分配内存空间,并将地址压入操作数栈顶;
         3: dup        //复制操作数栈顶值,并将其压入栈顶(也就是说目前操作数栈上有两个相同的指向堆的地址) 
         4: ldc           #3   //加载常量池中"test"到操作数栈顶
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V //弹出操作数栈顶两个数值,调用new String(String input)进行初始化
         9: astore_1 //将堆上新对象的地址赋值给变量1
        10: new           #5                  //新建StringBuilder类实例,即在堆内为该实例分配内存空间,并将地址压入操作数栈顶; 
        13: dup //复制操作数栈顶值,并将其压入栈顶(也就是说目前操作数栈上有两个相同的指向堆的地址)
        14: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V //弹出操作数栈顶值(StringBuilder新对象在内存上的地址,调用new StringBuilder()进行实例初始化)
        17: new           #2                  // 新建String类实例,即在堆内为该实例分配内存空间,并将地址压入操作数栈顶;
        20: dup //复制操作数栈顶值,并将其压入栈顶(也就是说目前操作数栈上有两个相同的指向堆内新String对象的地址) 
        21: ldc           #3                  //加载常量池中"test"到操作数栈顶
        23: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V 弹出操作数栈顶两个数值,调用new String(String input)进行初始化
        26: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; //调用StringBuilder.append(String input)方法,将String拼接到StringBuilder对象上
        29: aload_1 //加载变量b的值
        30: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; /调用StringBuilder.append(String input)方法,将变量b的值拼接到StringBuilder对象上
        33: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 调用StringBuilder的toString()方法
        36: astore_2 //将操作数栈顶的String赋值给变量c
        37: return
      LineNumberTable:
        line 6: 0
        line 9: 10
        line 11: 37
}

说明

  1. “test”字符串在常量池中只有一份
  2. String拼接(+)操作,jvm实际上转换成StringBuilder.append方法执行。
  3. 内存分配上,一共保存了1个常量池中的对象,和3个堆上的对象
  4. 拼接后形成的“testtest”并没有保存在常量池中

String d = c + b;

public class ReferenceTest {

    public static void main(String[] args){


        String b = new String("test");


        String c = new String("test1" ) + b;

        String d = c + b; //本例主要说明String变量拼接操作时,jvm的处理流程
    }
}

javap -v ReferenceTest.class```

public class ReferenceTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#20        // java/lang/Object."<init>":()V
   #2 = Class              #21            // java/lang/String
   #3 = String             #22            // test
   #4 = Methodref          #2.#23         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #24            // java/lang/StringBuilder
   #6 = Methodref          #5.#20         // java/lang/StringBuilder."<init>":()V
   #7 = String             #25            // test1
   #8 = Methodref          #5.#26         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #9 = Methodref          #5.#27         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Class              #28            // ReferenceTest
  #11 = Class              #29            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               SourceFile
  #19 = Utf8               ReferenceTest.java
  #20 = NameAndType        #12:#13        // "<init>":()V
  #21 = Utf8               java/lang/String
  #22 = Utf8               test
  #23 = NameAndType        #12:#30        // "<init>":(Ljava/lang/String;)V
  #24 = Utf8               java/lang/StringBuilder
  #25 = Utf8               test1
  #26 = NameAndType        #31:#32        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #27 = NameAndType        #33:#34        // toString:()Ljava/lang/String;
  #28 = Utf8               ReferenceTest
  #29 = Utf8               java/lang/Object
  #30 = Utf8               (Ljava/lang/String;)V
  #31 = Utf8               append
  #32 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Utf8               toString
  #34 = Utf8               ()Ljava/lang/String;
{
  public ReferenceTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=4, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: ldc           #3                  // String test
         6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: new           #5                  // class java/lang/StringBuilder
        13: dup
        14: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        17: new           #2                  // class java/lang/String
        20: dup
        21: ldc           #7                  // String test1
        23: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        26: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        29: aload_1
        30: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        33: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        36: astore_2
        37: new           #5                  // class java/lang/StringBuilder
        40: dup
        41: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        44: aload_2 // 加载变量c
        45: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        48: aload_1 //加载变量b
        49: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        52: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        55: astore_3 //赋值变量d
        56: return
      LineNumberTable:
        line 6: 0
        line 9: 10
        line 11: 37
        line 12: 56
}

说明
本例与上例的不同主要有两处:

  1. 常量池中分别保存"test"和"test1"
  2. String变量相加操作实际上就是分别加载两个变量值,进行StringBuilder.append()操作。
  3. 内存分配上,一共保存了2个常量池中的对象,和3个堆上的对象
  4. 拼接后形成的“test1test”并没有保存在常量池中

参考文档:
https://www.cnblogs.com/xiohao/p/4296088.html
https://www.cnblogs.com/ysocean/p/8571426.html
https://www.cnblogs.com/vinozly/p/5399308.html

相关文章

  • 【String类】对象内存分配详解

    关于内存分区参见:Java内存详解 - 内存分区 本文重点描述String对象创建时的内存分配方式。 字符串常量池...

  • 内存分配策略

    详解JVM对象分配内存 对象的内存分分配主要是指对上分配(也可栈上分配),对象主要分配在新生代Eden区,如果启动...

  • 自动化内存管理

    java中自动化内存管理的自动分为两类: 为对象分配内存 回收分配给对象的内存 对象分配的几个原则: 对象优先在新...

  • Day5 JVM内存分配

    类的加载:加载类,分配内存,初始化,设置对象头. 对象创建和内存分配 init方法就是按照我们的要求为属性赋值 有...

  • C#中String和StringBuilder的区别

    String和StringBuilder的区别 String类型对象的特点: 1.它是引用类型,在堆上分配内存2....

  • 对象的实例化

    类加载-》定位到类引用分配内存 指针碰撞 空闲列表初始化零值对象头分配 类指针 哈希码 GC年龄 锁标记对象定位 ...

  • 第4篇:C++ 高效的string_view

    string对象的性能问题 了解string对象的内存分配行为后,接下来我们如何考虑使用什么方法来避免字符串频繁的...

  • 【JVM】2、对象(Hot Spot虚拟机)

    对象的创建类加载分配内存方式方案对象初始化对象头(Object Header)设置创建完成 对象的内存分布对象头实...

  • Java基础-类对象创建过程

    类对象创建过程 1.为对象分配内存空间,接着为对象属性分配内存 2.为对象属性初始化默认值 3.为对象属性显式赋值...

  • String 内存分配

    String: final两层含义,1-不可以被继承,2-不可变。 最经典的比较: String s1 = new...

网友评论

      本文标题:【String类】对象内存分配详解

      本文链接:https://www.haomeiwen.com/subject/fwkkrqtx.html