java 字符串内存分配的分析与总结(推荐)_java

经常在网上各大版块都能看到对于java字符串运行时内存分配的探讨,形如:String a = "123",String b = new String("123"),这两种形式的字符串是存放在什么地方的呢,其实这两种形式的字符串字面值"123"本身在运行时既不是存放在栈上,也不是存放在堆上,他们是存放在方法区中的某个常量区,并且对于相同的字符串字面值在内存中只保留一份。下面我们将以实例来分析。

1.==运算符作用在两个字符串引用比较的两个案例:

public class StringTest {
  public static void main(String[] args) {
    //part 1
    String s1 = "i love china";
    String s2 = "i love china";
    System.out.println("result:" + s1 == s2);//程序运行结果为true
    //part 2
    String s3 = new String("i love china");
    String s4 = new String("i love china");
    System.out.println("result:" + s3 == s4);//程序运行结果为false
  }

}

我们知道java中==运算符比较的是变量的值,对于引用类型对应的变量的值存放的是引用对象的地址,在这里String是引用类型,这里面的四个变量的值存放的其实是指向字符串的地址。对于part2的执行结果是显然的,因为new操作符会使jvm在运行时在堆中创建新的对象,两个不同的对象的地址是不同的。但是由part1的执行结果,可以看出s1和s2是指向的同一个地址,那么由变量s1,s2指向的字符串是存放在什么地方的呢,jvm又是对字符串如何处理的呢。同样的对于变量s3,s4所指向的堆中的不同的字符串对象,他们会分别在自己的对象空间中保存一份"i love china"字符串吗,为了了解jvm是如何处理字符串,首先我们看java编译器生成的字节码指令。通过字节码指令我们来分析jvm将会执行哪些操作。

2.以下为程序生成的部分字节码信息。红色标注的是我们需要关注的部分。

Constant pool:
  #1 = Class       #2       // StringTest
  #2 = Utf8        StringTest
  #3 = Class       #4       // java/lang/Object
  #4 = Utf8        java/lang/Object
  #5 = Utf8        <init>
  #6 = Utf8        ()V
  #7 = Utf8        Code
  #8 = Methodref     #3.#9     // java/lang/Object."<init>":()V
  #9 = NameAndType    #5:#6     // "<init>":()V
 #10 = Utf8        LineNumberTable
 #11 = Utf8        LocalVariableTable
 #12 = Utf8        this
 #13 = Utf8        LStringTest;
 #14 = Utf8        main
 #15 = Utf8        ([Ljava/lang/String;)V
 #16 = String       #17      // i love china 字符串地址的引用
 #17 = Utf8        i love china
 #18 = Fieldref      #19.#21    // java/lang/System.out:Ljava/io/PrintStream;
 #19 = Class       #20      // java/lang/System
 #20 = Utf8        java/lang/System
 #21 = NameAndType    #22:#23    // out:Ljava/io/PrintStream;
 #22 = Utf8        out
 #23 = Utf8        Ljava/io/PrintStream;
 #24 = Class       #25      // java/lang/StringBuilder
 #25 = Utf8        java/lang/StringBuilder
 #26 = String       #27      // result:
 #27 = Utf8        result:

#28 = Methodref #24.#29 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#29 = NameAndType #5:#30 // "<init>":(Ljava/lang/String;)V
#30 = Utf8 (Ljava/lang/String;)V
#31 = Methodref #24.#32 // java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
#32 = NameAndType #33:#34 // append:(Z)Ljava/lang/StringBuilder;
#33 = Utf8 append
#34 = Utf8 (Z)Ljava/lang/StringBuilder;
#35 = Methodref #24.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#36 = NameAndType #37:#38 // toString:()Ljava/lang/String;
#37 = Utf8 toString
#38 = Utf8 ()Ljava/lang/String;
#39 = Methodref #40.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V
#40 = Class #41 // java/io/PrintStream
#41 = Utf8 java/io/PrintStream
#42 = NameAndType #43:#30 // println:(Ljava/lang/String;)V
#43 = Utf8 println
#44 = Class #45 // java/lang/String
#45 = Utf8 java/lang/String
#46 = Methodref #44.#29 // java/lang/String."<init>":(Ljava/lang/String;)V
#47 = Utf8 args
#48 = Utf8 [Ljava/lang/String;
#49 = Utf8 s1
#50 = Utf8 Ljava/lang/String;
#51 = Utf8 s2
#52 = Utf8 s3
#53 = Utf8 s4
#54 = Utf8 StackMapTable
#55 = Class #48 // "[Ljava/lang/String;"
#56 = Utf8 SourceFile
#57 = Utf8 StringTest.java
...........
//对应的方法的字节码指令,由jvm运行时解释执行。
 public static void main(java.lang.String[]);
  descriptor: ([Ljava/lang/String;)V
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
   stack=4, locals=5, args_size=1
     0: ldc      #16         // String i love china,该指令是将常量池的#16处符号引用,在这里为字符串“ilove china”符号引用push到栈顶。该指令与底下的指令2对应于程序中的String s1 = "i love china"语句
     2: astore_1             //将栈顶的对象引用赋值给局部变量1.
    3: ldc      #16         // String i love china,同0处的指令,指向的是同一个符号引用处的常量。该指令与底下的指令5对应于程序中的 String s2 = "i love china"语句。
     5: astore_2             //将栈顶的对象引用赋值给局部变量2.
    6: getstatic   #18         // Field java/lang/System.out:Ljava/io/PrintStream;
     9: new      #24         // class java/lang/StringBuilder
    12: dup
    13: ldc      #26         // String result:
    15: invokespecial #28         // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    18: aload_1
    19: aload_2
    20: if_acmpne   27         //弹出栈顶两个对象引用进行比较其是否相等,不等,转到指令27处,执行,相等执行下一条指令
    23: iconst_1
    24: goto     28
    27: iconst_0
    28: invokevirtual #31         // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
    31: invokevirtual #35         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    34: invokevirtual #39         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    37: new      #44         // class java/lang/String,创建一个对象,该对象位于常量池#44引用处,这里为String对象,并将对象引用push到栈顶。
    40: dup                //拷贝栈顶一份对象引用push到栈顶。
    41: ldc      #16         // String i love china,同0,3处指令。
    43: invokespecial #46         // Method java/lang/String."<init>":(Ljava/lang/String;)V
    46: astore_3
    47: new      #44         // class java/lang/String//创建一个对象,并将对象引用push到栈顶
    50: dup
    51: ldc      #16         // String i love china,  将字符串的符号引用push到栈顶。
    53: invokespecial #46         // Method java/lang/String."<init>":(Ljava/lang/String;)V,根据栈顶的对应的对象引用及字符串引用调用对象的init初始化方法,对字符串对象初始化
    56: astore    4           //将栈顶对象引用赋值给变量4.
    58: getstatic   #18         // Field java/lang/System.out:Ljava/io/PrintStream;
    61: new      #24         // class java/lang/StringBuilder
    64: dup
    65: ldc      #26         // String result:
    67: invokespecial #28         // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    70: aload_3
    71: aload     4
    73: if_acmpne   80
    76: iconst_1
    77: goto     81
    80: iconst_0
    81: invokevirtual #31         // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
    84: invokevirtual #35         // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    87: invokevirtual #39         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    90: return
.........

LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 11: 37
line 12: 47
line 13: 58
line 14: 90
LocalVariableTable:
Start Length Slot Name Signature
91 0 args [Ljava/lang/String;//局部变量0
88 1 s1 Ljava/lang/String; //局部变量1
85 2 s2 Ljava/lang/String;//局部变量2
44 3 s3 Ljava/lang/String;//局部变量3
33 4 s4 Ljava/lang/String;//局部变量4

字节码中红色的部分是与我们讨论相关的。通过生成的字节码,我们可以对示例程序得出如下结论。

1).  java编译器在将程序编译成字节码的过程中,对遇到的字符串常量"i love china"首先判断其是否在字节码常量池中存在,不存在创建一份,存在的话则不创建,也就是相等的字符串,只保留一份,通过符号引用可以找到它,这样使得程序中的字符串变量s1和s2都是指向常量池中的同一个字符串常量。在运行时jvm会将字节码常量池中的字符串常量存放在方法区中的通常称之为常量池的位置,并且字符串是以字符数组的形式通过索引来访问的。jvm在运行时将s1与s2指向的字符串相对引用地址指向字符串实际的内存地址。

2).  对于String s3 = new String("i love china"),String s4 = new String("i love china"),由字节码可以看出其是调用了new指令,jvm会在运行时创建两个不同的对象,s3与s4指向的是不同的对象地址。所以s3==s4比较的结果为false。

其次,对于s3与s4对象的初始化,从字节码看出是调用对象的init方法并且传递的是常量池中”i love china”的引用,那么创建String对象及初始化究竟干了什么,我们可以查看通过查看String的源码及String对象生成的字节码,以便更好的了解对于new String("i love china")时,在对象内部是做了字符串的拷贝还是直接指向该字符串对应的常量池的地址的引用。

3.String对象的部分源码:

<SPAN style="FONT-SIZE: 14pt">public final class String
  implements java.io.Serializable, Comparable<String>, CharSequence {
  /** The value is used for character storage. */
  private final char value[]; 

  /** Cache the hash code for the string */
  private int hash; // Default to 0 

  public String() {
    this.value = new char[0];
  }</SPAN>
  <SPAN style="BACKGROUND-COLOR: #ffffff; FONT-SIZE: 18pt"> public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
  }
</SPAN>

从源码中我们看到String类里有个实例变量 char value[],通过构造方法我们可知,对象在初始化时并没有做拷贝操作,只是将传递进来的字符串对象的地址引用赋给了实例变量value。由此我们可以初步的得出结论:即使使用new String("abc")创建了一个字符串对象时,在内存堆中为该对象分配了空间,但是在堆上并没有存储"abc"本身的任何信息,只是初始化了其内部的实例变量到"abc"字符串的引用。其实这样做也是为了节省内存的存储空间,以及提高程序的性能。

4.下面我们来看看String对象部分字节码信息:

public java.lang.String();
  descriptor: ()V
  flags: ACC_PUBLIC
  Code:
   stack=2, locals=1, args_size=1
     0: aload_0
     1: invokespecial #1         // Method java/lang/Object."<init>":()V
     4: aload_0
     5: iconst_0
     6: newarray    char
     8: putfield   #2         // Field value:[C
    11: return
   LineNumberTable:
    line 137: 0
    line 138: 4
    line 139: 11

 public java.lang.String(java.lang.String);
  descriptor: (Ljava/lang/String;)V
  flags: ACC_PUBLIC
  Code:
   stack=2, locals=2, args_size=2
     0: aload_0              //将局部变量0push到栈顶,自身对象的引用。
     1: invokespecial #1         // Method java/lang/Object."<init>":()V 弹出栈顶对象引用调用该对象的#1处的初始化方法。
     4: aload_0              //将自身对象引用push到栈顶。
     5: aload_1              //传递的字符串引用push到栈顶。
     6: getfield   #2         // Field value:[C // 弹出栈顶的字符串引用并将其赋值给#2处的实例变量,并将其存放到栈上。
     9: putfield   #2         // Field value:[C // 弹出栈顶的字符串引用及对象自身的引用并将字符串的引用赋值给本对象自身的实例变量。
    12: aload_0
    13: aload_1
    14: getfield   #3         // Field hash:I
    17: putfield   #3         // Field hash:I
    20: return

从字节码的角度我们可以得出结论,new String("abc")在构造新对象时执行的是字符串引用的赋值,而不是字符串的拷贝。以上是从源码及字节码的角度来对字符串的内存分配进行的分析与总结。

以上这篇java 字符串内存分配的分析与总结(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索字符串分配内存
c语言 字符串分配内存、字符串 动态分配内存、java 内存分配、java xmx 内存分配、java内存分配机制,以便于您获取更多的相关知识。

时间: 2024-08-31 00:02:46

java 字符串内存分配的分析与总结(推荐)_java的相关文章

JAVA虚拟机内存分配与回收机制

Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等,还具有相应的指令系统. Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存

关于Java 数组内存分配一点认识

 可能Java 数组大家都很熟悉,最近我遇到了一个关于Java 数组内存分配的问题.         呵呵.突然就发现许多书上"基本数据类型存储在栈内存当中,对象则保存在堆内存"这句话完全是错误的.下面是个简单的例子代码: public class Test { public static void main(String[] argv) { // 静态初始化数组 String[] names = { "Michael", "Orson", &q

有关VB的字符串内存分配的问题

问题描述 有关VB的字符串内存分配的问题 用C++开发的DLL型函数库,C++的Struct中包含字符串,在VB中转换成Type型,关于内存的使用是怎么样的定义 解决方案 http://blog.csdn.net/jiftlixu/article/details/5351741 解决方案二: 关于字符串的内存分配问题 解决方案三: VB中没有纠结过内存分配的问题,在VB中TYPE就是一个自定义数据类型. 不同类型的变量可以组合起来用来创建用户定义的类型(如熟知的 C 编程语言中的 structs

基于Java 数组内存分配的相关问题_java

可能Java 数组大家都很熟悉,最近我遇到了一个关于Java 数组内存分配的问题.呵呵.突然就发现许多书上"基本数据类型存储在栈内存当中,对象则保存在堆内存"这句话完全是错误的.下面是个简单的例子代码: 复制代码 代码如下: public class Test {    public static void main(String[] argv) {// 静态初始化数组String[] names = { "Michael", "Orson",

Java常见内存溢出异常分析与解决_java

Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆内存分为了三部分,新生代,老年代,持久带,其中持久带实现了规范中规定的方法区,而内存模型中不同的部分都会出现相应的OutOfMemoryError错误,接下来我们就分开来讨论一下.java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因: JVM内存过小.程序不严密,产生了过多的垃圾. 导致OutOfMemor

Java中内存分配的几种方法_java

一.数组分配的上限 Java里数组的大小是受限制的,因为它使用的是int类型作为数组下标.这意味着你无法申请超过Integer.MAX_VALUE(2^31-1)大小的数组.这并不是说你申请内存的上限就是2G.你可以申请一个大一点的类型的数组.比如: 复制代码 代码如下: final long[] ar = new long[ Integer.MAX_VALUE ]; 这个会分配16G -8字节,如果你设置的-Xmx参数足够大的话(通常你的堆至少得保留50%以上的空间,也就是说分配16G的内存,

java中内存分配策略及堆和栈的比较[转]

2.1 内存分配策略 按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允 许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据

java字符串数组进行大小排序的简单实现_java

若是将两个字符串直接比较大小,会包:The operator > is undefined for the argument type(s) java.lang.String, java.lang.String的错误. 字符串比较大小可以用字符串长度或者是比较字符串内字符的ASCII码值,前者太简单,就不进行讲述记录. 字符串用ASCII码比较大小,规则是: 1.比较首字母的ASCII码大小 2.若是前面的字母相同,则比较之后的字母的ASCII码值 3.若是一个字符串从首字母开始包含另一个字符串

java中数组的定义及使用方法(推荐)_java

数组:是一组相关变量的集合 数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组.二维数组.多维数组 数据的有点 不使用数组定义100个整形变量:int i1;int i2;int i3 使用数组定义 int i[100]; 数组定义:int i[100];只是一个伪代码,只是表示含义的 一维数组 一维数组可以存放上千万个数据,并且这些数据的类型是完全相同的, 使用java数组,必须经过两个步骤,声明数组和分配内存给该数组, 声明形式一 声明一维数组:数据类型