项目地址
jvm_03
前面介绍了jvm内存结构,现在我们来介绍一下常量池。我们都知道常量的对的定义。常量是一个固定的值,不会发生改变。比如:配置文件,数据库连接池基本配置等等。
常量池
常量的定义在上面提到过了,那我们来了解一下常量池,简单的看就是存储常量的地方。
这是百度百科的解释:
常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,
方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;当然也可扩
充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。
看到这里我们也应该知道了,常量池是处于方法区。但是事实不是全部的常量池都在方法区。常量池分为
- class常量池(静态常量池)
核心概念:定义字面量(自己定义的变量)、符号引用(类的全限定名–>包名+类名) - 运行常量池
classLoad将静态常量池中会被需要的常量放入运行常量池 - 字符串常量池
在jdk1.6和以前放在方法区(永久区);
在jdk1.7常量池放入到堆中
在jdk1.8时放在堆内存中的字符串常量池,其他的常量都是放在元空间
为什么一个字符串常量池的位置入池该来该去?最后改到jdk1.8的情况。主要是为了方便GC回收。
那为什么要常量池放到堆中,因为如果放在方法区,GC不会经常的回收,所以耗费内存。那为什么jdk1.7把常量池放到了堆中,还不满足呢?因为常量池中不只有局部变量,也有全局变量,总不可能吧全局变量提前给回收了吧? 所以最后只把字符串常量池放到堆中。主要就是为了防止全局常量也会在堆中被回收。
我们在看一些文章的时候,总会说new String、String、String+String。的区别。博主使用以下代码来说。
/**
* @author 龙小虬
* @date 2021/4/13 14:35
*/
public class Main {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
}
}
这段代码的结果是什么呢?片面的说什么对象、常量很难记住。我们直接看汇编代码吧。(javap -c -v C:\Users\HP\IdeaProjects\jvm_03\target\classes\Main.class
)
之别反汇编。
可以看到结果。
public class Main
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#34 // java/lang/Object."<init>":()V
#2 = String #35 // a
#3 = String #36 // b
#4 = String #37 // ab
#5 = Class #38 // java/lang/StringBuilder
#6 = Methodref #5.#34 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#40 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #41.#42 // java/lang/String.intern:()Ljava/lang/String;
#10 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream;
#11 = Methodref #45.#46 // java/io/PrintStream.println:(Z)V
#12 = Class #47 // Main
#13 = Class #48 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 LMain;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 s1
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 s2
#28 = Utf8 s3
#29 = Utf8 s4
#30 = Utf8 s5
#31 = Utf8 s6
#32 = Utf8 SourceFile
#33 = Utf8 Main.java
#34 = NameAndType #14:#15 // "<init>":()V
#35 = Utf8 a
#36 = Utf8 b
#37 = Utf8 ab
#38 = Utf8 java/lang/StringBuilder
#39 = NameAndType #49:#50 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#40 = NameAndType #51:#52 // toString:()Ljava/lang/String;
#41 = Class #53 // java/lang/String
#42 = NameAndType #54:#52 // intern:()Ljava/lang/String;
#43 = Class #55 // java/lang/System
#44 = NameAndType #56:#57 // out:Ljava/io/PrintStream;
#45 = Class #58 // java/io/PrintStream
#46 = NameAndType #59:#60 // println:(Z)V
#47 = Utf8 Main
#48 = Utf8 java/lang/Object
#49 = Utf8 append
#50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#51 = Utf8 toString
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 java/lang/String
#54 = Utf8 intern
#55 = Utf8 java/lang/System
#56 = Utf8 out
#57 = Utf8 Ljava/io/PrintStream;
#58 = Utf8 java/io/PrintStream
#59 = Utf8 println
#60 = Utf8 (Z)V
{
public Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=7, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: aload 4
35: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
38: astore 6
40: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_3
44: aload 4
46: if_acmpne 53
49: iconst_1
50: goto 54
53: iconst_0
54: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
57: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
60: aload_3
61: aload 5
63: if_acmpne 70
66: iconst_1
67: goto 71
70: iconst_0
71: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
74: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
77: aload_3
78: aload 6
80: if_acmpne 87
83: iconst_1
84: goto 88
87: iconst_0
88: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
91: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 9
line 11: 29
line 12: 33
line 13: 40
line 14: 57
line 15: 74
line 16: 91
LocalVariableTable:
Start Length Slot Name Signature
0 92 0 args [Ljava/lang/String;
3 89 1 s1 Ljava/lang/String;
6 86 2 s2 Ljava/lang/String;
9 83 3 s3 Ljava/lang/String;
29 63 4 s4 Ljava/lang/String;
33 59 5 s5 Ljava/lang/String;
40 52 6 s6 Ljava/lang/String;
}
这样看的话,代码量有点大。我们分开理解吧。
/**
* @author 龙小虬
* @date 2021/4/13 14:35
*/
public class Main {
public static void main(String[] args) {
System.out.println("lxq");
}
}
先反汇编此段代码。
public class Main
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // lxq
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // Main
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LMain;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 Main.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 lxq
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 Main
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String lxq
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
Constant pool:
这里看一眼应该都知道是什么吧?------常量池
那我们先找到我们的main()方法。
public static void main(java.lang.String[]);
应该很好找到的,就是这里,我们的main()方法。
这里就是我们的main()方法中的汇编代码
那么怎么看呢?
我们可以看到其中有#2、#3、#4
这就是我们前面提到的常量池里面前面那一部分吧?那前面的0、3、5、8
是什么呢?这个就是我们的栈帧。废话不多说。直接看怎么去查询。找到#2
。
这里要我们去查找#22 #23
同理。
这里可以看到了这是我们java输出代码前面的一部分System.out
现在就拿到了System.out
,再去看#3
这里拿到了我们的字符串常量。
在看到#4
获取到println,然后将字符串输出。
基本的查找应该都了解了。那么我们就来看看。
/**
* @author 龙小虬
* @date 2021/4/13 14:35
*/
public class Main {
public static void main(String[] args) {
String a1 = "a";
String b1 = "b";
String ab1 = "ab";
String ab2 = a1 + b1;
System.out.println(ab1 == ab2);
}
}
那么他们会相等吗?
我们先看汇编代码。
public class Main
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #12.#31 // java/lang/Object."<init>":()V
#2 = String #32 // a
#3 = String #33 // b
#4 = String #34 // ab
#5 = Class #35 // java/lang/StringBuilder
#6 = Methodref #5.#31 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#36 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Fieldref #38.#39 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #40.#41 // java/io/PrintStream.println:(Z)V
#11 = Class #42 // Main
#12 = Class #43 // java/lang/Object
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 LMain;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 a1
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 b1
#27 = Utf8 ab1
#28 = Utf8 ab2
#29 = Utf8 SourceFile
#30 = Utf8 Main.java
#31 = NameAndType #13:#14 // "<init>":()V
#32 = Utf8 a
#33 = Utf8 b
#34 = Utf8 ab
#35 = Utf8 java/lang/StringBuilder
#36 = NameAndType #44:#45 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#37 = NameAndType #46:#47 // toString:()Ljava/lang/String;
#38 = Class #48 // java/lang/System
#39 = NameAndType #49:#50 // out:Ljava/io/PrintStream;
#40 = Class #51 // java/io/PrintStream
#41 = NameAndType #52:#53 // println:(Z)V
#42 = Utf8 Main
#43 = Utf8 java/lang/Object
#44 = Utf8 append
#45 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#46 = Utf8 toString
#47 = Utf8 ()Ljava/lang/String;
#48 = Utf8 java/lang/System
#49 = Utf8 out
#50 = Utf8 Ljava/io/PrintStream;
#51 = Utf8 java/io/PrintStream
#52 = Utf8 println
#53 = Utf8 (Z)V
{
public Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 9
line 11: 29
line 12: 46
LocalVariableTable:
Start Length Slot Name Signature
0 47 0 args [Ljava/lang/String;
3 44 1 a1 Ljava/lang/String;
6 41 2 b1 Ljava/lang/String;
9 38 3 ab1 Ljava/lang/String;
29 18 4 ab2 Ljava/lang/String;
}
傻了,还是很多,但是我们了解了怎么去看,这就很简单了。
照常看到
一行一行查看。
#2
#3
#4
这里就是三个常量的地址。
而astore_1、 astore_2、 astore_3就得查询汇编指令了。
诶???这里怎么看到一个new ?那我们继续去找找为什么。
StringBuild?我们没有使用他啊。
再往下看
#13
init,#14
()。这应该就是代表无参构造吧?
这就是调用刚刚创建的StringBuild进行append()吧?将两个常量进行append()
if_acmpne
有条件转移。那也就是转移到42咯,
#8
这也就让我们知道了a1 + b1;
其实就是new StringBuild()之后进行了append(a1)和append(b1),之后再进行toString()。
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(a1);
stringBuilder.append(b1);
stringBuilder.toString();
那他这里已经进行了toString(),那么我们应该拿到的是值啊,为什么会不相等?我们先看看,这个toString()方法。
原来他返回的是一个对象,返回的时候直接new String()了。而在前面的文章提到过,进行new 的对象,是放在堆内存中的,并非字符串常量池,所以他们根本不会相等(因为“==”比较的是地址)
所以输出一定是false。
我们也可以看到运行结果也是false。
既然这个可以看懂了,那么我们再来看一个。
/**
* @author 龙小虬
* @date 2021/4/13 14:35
*/
public class Main {
public static void main(String[] args) {
String ab1 = "ab";
String ab2 = "a" + "b";
System.out.println(ab1 == ab2);
}
}
变成汇编代码:
public class Main
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = String #24 // ab
#3 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #27.#28 // java/io/PrintStream.println:(Z)V
#5 = Class #29 // Main
#6 = Class #30 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LMain;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 ab1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 ab2
#21 = Utf8 SourceFile
#22 = Utf8 Main.java
#23 = NameAndType #7:#8 // "<init>":()V
#24 = Utf8 ab
#25 = Class #31 // java/lang/System
#26 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(Z)V
#29 = Utf8 Main
#30 = Utf8 java/lang/Object
#31 = Utf8 java/lang/System
#32 = Utf8 out
#33 = Utf8 Ljava/io/PrintStream;
#34 = Utf8 java/io/PrintStream
#35 = Utf8 println
#36 = Utf8 (Z)V
{
public Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
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 ab
2: astore_1
3: ldc #2 // String ab
5: astore_2
6: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: aload_2
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
22: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 args [Ljava/lang/String;
3 20 1 ab1 Ljava/lang/String;
6 17 2 ab2 Ljava/lang/String;
}
看到
可以发现他们寻找的地址都是一样的,按照上面的寻找方法,那肯定就是两个地址一模一样的
返回的肯定为true。
这个我们懂了之后,再来看一个方法。
intern()
这个方法很奇怪,他可以将我们对象放入到字符串常量池中,如果常量池中存在 则不会放入。我们来验证一下吧。
/**
* @author 龙小虬
* @date 2021/4/13 14:35
*/
public class Main {
public static void main(String[] args) {
String a1 = "a";
String b1 = "b";
String ab = a1 + b1;
String ab2 = "ab";
System.out.println(ab == ab2);
String ab1 = ab.intern();
System.out.println(ab1 == ab2);
}
}
汇编代码:
public class Main
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#33 // java/lang/Object."<init>":()V
#2 = String #34 // a
#3 = String #35 // b
#4 = Class #36 // java/lang/StringBuilder
#5 = Methodref #4.#33 // java/lang/StringBuilder."<init>":()V
#6 = Methodref #4.#37 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#7 = Methodref #4.#38 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#8 = String #28 // ab
#9 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #41.#42 // java/io/PrintStream.println:(Z)V
#11 = Methodref #43.#44 // java/lang/String.intern:()Ljava/lang/String;
#12 = Class #45 // Main
#13 = Class #46 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 LMain;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 a1
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 b1
#28 = Utf8 ab
#29 = Utf8 ab2
#30 = Utf8 ab1
#31 = Utf8 SourceFile
#32 = Utf8 Main.java
#33 = NameAndType #14:#15 // "<init>":()V
#34 = Utf8 a
#35 = Utf8 b
#36 = Utf8 java/lang/StringBuilder
#37 = NameAndType #47:#48 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = NameAndType #49:#50 // toString:()Ljava/lang/String;
#39 = Class #51 // java/lang/System
#40 = NameAndType #52:#53 // out:Ljava/io/PrintStream;
#41 = Class #54 // java/io/PrintStream
#42 = NameAndType #55:#56 // println:(Z)V
#43 = Class #57 // java/lang/String
#44 = NameAndType #58:#50 // intern:()Ljava/lang/String;
#45 = Utf8 Main
#46 = Utf8 java/lang/Object
#47 = Utf8 append
#48 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#49 = Utf8 toString
#50 = Utf8 ()Ljava/lang/String;
#51 = Utf8 java/lang/System
#52 = Utf8 out
#53 = Utf8 Ljava/io/PrintStream;
#54 = Utf8 java/io/PrintStream
#55 = Utf8 println
#56 = Utf8 (Z)V
#57 = Utf8 java/lang/String
#58 = Utf8 intern
{
public Main();
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 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=6, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Strin
gBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: ldc #8 // String ab
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: aload_3
47: invokevirtual #11 // Method java/lang/String.intern:()Ljava/lang/String;
50: astore 5
52: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
55: aload 5
57: aload 4
59: if_acmpne 66
62: iconst_1
63: goto 67
66: iconst_0
67: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
70: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 25
line 11: 29
line 12: 46
line 13: 52
line 14: 70
LocalVariableTable:
Start Length Slot Name Signature
0 71 0 args [Ljava/lang/String;
3 68 1 a1 Ljava/lang/String;
6 65 2 b1 Ljava/lang/String;
25 46 3 ab Ljava/lang/String;
29 42 4 ab2 Ljava/lang/String;
52 19 5 ab1 Ljava/lang/String;
}
我们主要看到这个,其他的都很好理解
将new String()又进行转换,变成了字符串,赋值给ab1
。所以他们肯定是相等的。毕竟都在字符串常量池了。
运行结果:
下面我们了解了常量池,和String字符串的比较,那就再来看看内存溢出和内存泄露吧。
内存溢出
概念:在申请内存之前,内存不够需求,供小于求
- 堆内存溢出
arraylist存数据
代码示例:
import java.util.ArrayList;
/**
* @author 龙小虬
* @date 2021/4/13 14:35
* -Xmx8m
*/
public class HeapTest {
public static void main(String[] args) {
int i = 0;
try {
ArrayList<String> strings = new ArrayList<String>();
while (true) {
strings.add("mayikt");
i++;
}
} catch (Exception e) {
}
}
}
- 方法区
定义常量过多(一般发生在项目启动)
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* @author 龙小虬
* @date 2021/4/13 22:40
* -XX:MaxMetaspaceSize=8m -XX:-UseCompressedClassPointers
*/
public class MetaspaceTest extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
MetaspaceTest test = new MetaspaceTest();
for (int i = 0; i < 30000; i++, j++) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
byte[] code = cw.toByteArray();
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.out.println(j);
}
}
}
- 本地方法栈
/**
* @author 龙小虬
* @date 2021/4/13 22:45
* -Xss256k
*/
public class StackTest {
private static int count;
public static void main(String[] args) {
lxq();
}
private static void lxq() {
count++;
System.out.println("count:" + count);
lxq();
}
}
内存泄露
概念:在申请内存之后,一直无法被GC回收,有可能会发生内存溢出问题(发生在程序运行一段时间)。
比如:HashMap将自定义对象为key
import java.util.HashMap;
/**
* @author 龙小虬
* @date 2021/4/13 22:47
* -Xmx3M -Xms3M
*/
public class HashMapMemoryLeak {
public static void main(String[] args) {
HashMap<HashKey, Integer> map = new HashMap<HashKey, Integer>(1000);
int counter = 0;
while (true) {
//循环插入新对象 new出很多很多内存地址不等的对象但是
HashKey p = new HashKey("lxq", "lxq666");
map.put(p, 1);
counter++;
if (counter % 1000 == 0) {
System.out.println("map size: " + map.size());
System.out.println("运行" + counter
+ "次后,可用内存剩余" + Runtime.getRuntime().freeMemory() / (1024 * 1024) + "MB");
}
}
}
}
class HashKey {
private final String id;
private String name;
public HashKey(String name, String id) {
this.name = name;
this.id = id;
}
public void setName(String name) {
this.name = name;
}
// @Override
// public int hashCode() {
// return id.hashCode();
// }
//
// @Override
// public boolean equals(Object obj) {
// if (obj instanceof HashKey)
// return name.equals(((HashKey) obj).name) && id.equals(((HashKey) obj).id);
// else
// return false;
// }
}
没有将自定义的对象没有重写hashcode()、equals()方法。所以会在运行过程中发生内存溢出,局部变量p一直无法回收。