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
0 91 0 args [Ljava/lang/String;//局部变量0
3 88 1 s1 Ljava/lang/String; //局部变量1
6 85 2 s2 Ljava/lang/String;//局部变量2
47 44 3 s3 Ljava/lang/String;//局部变量3
58 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对象的部分源码:
- 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];
- }
- public String(String original) {
- this.value = original.value;
- this.hash = original.hash;
- }
从源码中我们看到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 字符串内存分配的分析与总结的更多相关文章
- JAVA虚拟机内存分配与回收机制
Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等 ...
- java继承内存分配
java继承内存分配 继承的基本概念: * Java不支持多继承,也就是说子类至多只能有一个父类. * 子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法. * 子类中定义的成员 ...
- 图解Java继承内存分配
图解Java继承内存分配 继承的基本概念: (1)Java不支持多继承,也就是说子类至多只能有一个父类. (2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法. (3)子 ...
- (转载)图解Java多态内存分配以及多态中成员方法的特点
图解Java多态内存分配以及多态中成员方法的特点 图解Java多态内存分配以及多态中成员方法的特点 Person worker = new Worker(); 子类实例对象地址赋值给父类类型引 ...
- JAVA中内存分配的问题
JAVA中内存分配的问题 1. 有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构.说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出 ...
- java中内存分配策略及堆和栈的比较
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- Java 对象内存分配与回收
JVM内存区域模型: * 程序计数器,内存区域极小,是当前线程的字节码执行行号指示器: * 虚拟机栈.本地方法栈,即平时所说的“栈”,是虚拟机用来执行方法(包括Java.非Java方法)时,使用的临时 ...
- map的内存分配机制分析
该程序演示了map在形成的时候对内存的操作和分配. 因为自己对平衡二叉树的创建细节理解不够,还不太明白程序所显示的日志.等我明白了,再来修改这个文档. /* 功能说明: map的内存分配机制分析. 代 ...
- list的内存分配机制分析
该程序演示了list在内存分配时候的问题.里面的备注信息是我的想法. /* 功能说明: list的内存分配机制分析. 代码说明: list所管理的内存地址可以是不连续的.程序在不断的push_back ...
随机推荐
- matlab 读写其他格式数据文件(excel)
1. excel matlab和excel 中的数据互相导入 xlswrite() mat ⇒ excel 请问怎么把大容量的mat文件导出到excel文件中 – MATLAB中文论坛 % data. ...
- 使用c++代替使用的c包!
看到很多人写c代码.的结构为包的各种元件.例如,使用转载如下epoll样本: // // a simple echo server using epoll in linux // // ...
- Codeforces 458A Golden System
经过计算两个字符串的大小对比 主要q^2=q+1 明明是斐波那契数 100000位肯定超LL 我在每一位仅仅取到两个以内 竟然ac了 #include<bits/stdc++.h> usi ...
- WPF中的 Layout To Layout
原文:WPF中的 Layout To Layout WPF中的 Layout To Layout 周银辉 WPF的布局功能异常强大,当有时我 ...
- 《C++ Primer Plus》学习笔记11
<C++ Primer Plus>学习笔记11 第17章 输入.输出和文件 <<<<<<<<<<<<<< ...
- HDU - 2294 Pendant (DP滚动数组降维+矩阵高速功率)
Description On Saint Valentine's Day, Alex imagined to present a special pendant to his girl friend ...
- pdf密码解除工具
PDF Password Remover 3.0下载地址: 链接:https://pan.baidu.com/s/1hAmcGB-vMxz79IGGskdzHQ 提取码:q6y8
- Qt 事件处理 快捷键(重写eventFilter的函数,使用Qt::ControlModifier判断)
CTRL+Enter发送信息的实现 在现在的即时聊天程序中,一般都设置有快捷键来实现一些常用的功能,类似QQ可以用CTRL+Enter来实现信息的发送. 在QT4中,所有的事件都继承与QEvent这个 ...
- TIME WINAPI
GetDynamicTimeZoneInformation https://msdn.microsoft.com/en-us/library/windows/desktop/ms724318(v=vs ...
- OpenGL与Directx的区别
OpenGL 只是图形函数库. DirectX 包含图形, 声音, 输入, 网络等模块. 单就图形而论, DirectX 的图形库性能不如 OpenGL OpenGL稳定,可跨平台使用.但 OpenG ...