- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章java 字符串内存分配的分析与总结(推荐)由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
经常在网上各大版块都能看到对于java字符串运行时内存分配的探讨,形如:String a = "123",String b = new String("123"),这两种形式的字符串是存放在什么地方的呢,其实这两种形式的字符串字面值"123"本身在运行时既不是存放在栈上,也不是存放在堆上,他们是存放在方法区中的某个常量区,并且对于相同的字符串字面值在内存中只保留一份。下面我们将以实例来分析.
1.==运算符作用在两个字符串引用比较的两个案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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.以下为程序生成的部分字节码信息。红色标注的是我们需要关注的部分.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
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对象的部分源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<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对象部分字节码信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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 字符串内存分配的分析与总结(推荐)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个应用程序,它会抛出 GKSession 并在各种条件下(连接超时、 session 失败等)创建一个新的 GKSession。不过,我遇到了内存泄漏问题,并且有时会在重新连接几次循环后崩溃。
比如我在宿主代码中有一个浮点指针 float *p 是否可以确定他指向的内存类型(设备/主机)? 最佳答案 在 UVA system 中, 运行时 API 函数 cudaPointerGetAttri
我已将项目转换为 .Net 4.0 并且以下代码不起作用: typeof(RuntimeTypeHandle).GetMethod("Allocate", BindingFlags.Instance
当我声明 char ch = 'ab' 时,ch 只包含 'b',为什么它不存储 'a'? #include int main() { char ch = 'ab'; printf("%c"
我对 Disk Sector 和 Block 有疑问。扇区是一个单位,通常为 512 字节或 1k、2k、4k 等取决于硬件。文件系统 block 大小是一组扇区大小。 假设我正在存储一个 5KB 的
假设我有 8 个人和5000 个苹果。 我想将所有苹果分发给所有 8 个人,这样我就没有苹果了。 但每个人都应该得到不同数量 将它们全部分发出去的最佳方式是什么? 我是这样开始的: let peopl
我正在构建的网站顶部有一个搜索栏。与 Trello 或 Gmail 类似,我希望当用户按下“/”键时,他们的焦点就会转到该搜索框。 我的 JavaScript 看起来像这样: document.onk
我有一小段代码: if (PZ_APP.dom.isAnyDomElement($textInputs)){ $textInputs.on("focus", function(){
我观察到以下行为。 接受了两个属性变量。 @property (nonatomic, retain) NSString *stringOne; @property (nonatomic, assign
我正在解决这样的问题 - 实现一个计算由以下内容组成的表达式的函数以下操作数:“(”、“)”、“+”、“-”、“*”、“/”。中的每个数字表达式可能很大(与由字符串表示的一样大)1000 位)。 “/
我有一组主机和一组任务。 每个主机都有 cpu、mem 和任务容量,每个任务都有 cpu、mem 要求。 每个主机都属于一个延迟类别,并且可以与具有特定延迟值的其他主机通信。 每个任务可能需要以等于或
该程序的作用:从文件中读取一个包含 nrRows 行和 nrColomns 列的矩阵(二维数组)。矩阵的所有元素都是 [0,100) 之间的整数。程序必须重新排列矩阵内的所有元素,使每个元素等于其所在
世界!我有个问题。今天我尝试创建一个代码,它可以找到加泰罗尼亚语号码。但是在我的程序中可以是长数字。我找到了分子和分母。但我不能分割长数字!此外,只有标准库必须在此程序中使用。请帮帮我。这是我的代码
我确定我遗漏了一些明显的东西,但我想在 Objective C 中创建一个 NSInteger 指针的实例。 -(NSInteger*) getIntegerPointer{ NSInteger
这个问题在这里已经有了答案: Difference between self.ivar and ivar? (4 个答案) 关闭 9 年前。
我如何将 v[i] 分配给一系列整数(v 的类型是 vector )而无需最初填充 最佳答案 你的意思是将 std::vector 初始化为一系列整数? int i[] = {1, 2, 3, 4,
我想寻求分配方面的帮助....我把这个作业带到了学校......我必须编写程序来加载一个 G 矩阵和第二个 G 矩阵,并搜索第二个 G 矩阵以获取存在数第一个 G 矩阵的......但是,当我尝试运行
我必须管理资源。它基本上是一个唯一的编号,用于标识交换机中的第 2 层连接。可以有 16k 个这样的连接,因此每次用户希望配置连接时,他/她都需要分配一个唯一索引。同样,当用户希望删除连接时,资源(号
是否有任何通用的命名约定来区分已分配和未分配的字符串?我正在寻找的是希望类似于 us/s 来自 Making Wrong Code Look Wrong ,但我宁愿使用常见的东西也不愿自己动手。 最佳
我需要读取一个 .txt 文件并将文件中的每个单词分配到一个结构中,该结构从结构 vector 指向。我将在下面更好地解释。 感谢您的帮助。 我的程序只分配文件的第一个字... 我知道问题出在函数 i
我是一名优秀的程序员,十分优秀!