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 ...
随机推荐
- OpenCV中基于Haar特征和级联分类器的人脸检测
使用机器学习的方法进行人脸检测的第一步需要训练人脸分类器,这是一个耗时耗力的过程,需要收集大量的正负样本,并且样本质量的好坏对结果影响巨大,如果样本没有处理好,再优秀的机器学习分类算法都是零. 今年3 ...
- Open SSL 开发环境配置
Open SSL 开发环境配置 最后更新日期:2014-05-13 阅读前提:VisualStudio的基本使用.Cent OS的基本使用 环境: Windows 8.1 64bit英文版,Visua ...
- 一些自成系统、完备的教程(链接、博客、github等)
0. Linus shell Advanced Bash-Scripting Guide 1. latex Some applicable LATEX's info 14 课的个人 CV 制作: 15 ...
- 避免让WPF资源字典变得杂乱臃肿
原文:避免让WPF资源字典变得杂乱臃肿 避免让WPF资源字典变得杂乱臃肿 周银辉 今天看到项目种的一个XXXResource.xaml文件代码 ...
- WPF 悬浮键盘
原文:WPF 悬浮键盘 public class TouchScreenKeyboard : Window { #region Property & Variable & Constr ...
- vs2015 生成 cordova 页面中文乱码
原文:vs2015 生成 cordova 页面中文乱码 1.用VS2015新创建Cordova项目,启动运行index.html 中文显示乱码 解决方案: 1.使用text/html通用解析编码utf ...
- 谷歌将为 Mac 和 Windows 用户推出新的备份和同步应用
据报道,谷歌将于 6 月 28 日面向 Mac 和 Windows 用户发布一款新的备份和同步应用(Backup and Sync app). Google 刚刚宣布将推出其备份和同步应用程序,该工具 ...
- Win8Metro(C#)数字图像处理--2.27图像加法运算
原文:Win8Metro(C#)数字图像处理--2.27图像加法运算 [函数名称] 图像加法函数AddProcess(WriteableBitmap src, WriteableBitmap a ...
- Win10版《芒果TV》全平台直播第89届奥斯卡颁奖典礼,特设第二演播室带来一手资讯
芒果TV为所有中国影迷们带来的:今年的奥斯卡直播与往年格外不同,为了让网友们观看这场盛典得到多维度体验,不管是来看热闹的还是看门道的都看得开心尽兴,芒果TV特设第二演播室,为大家带来第一手新鲜热辣的现 ...
- Oracle序列使用:建立、删除、使用
Oracle序列使用:建立.删除 在开始讲解Oracle序列使用方法之前,先加一点关于Oracle client sqlplus的使用,就是如果执行多行语句的话一定要加“/”才能表示结束,并执行!本篇 ...