jvm內(nèi)存結(jié)構(gòu)
1.程序計(jì)數(shù)器
1.1 定義
Program Counter Register 程序計(jì)數(shù)器(寄存器)
- 作用,記住下一條jvm指令的執(zhí)行地址
- 特點(diǎn)
- 是線程私有的
- (唯一)不會(huì)存在內(nèi)存溢出
1.2 作用
二進(jìn)制字節(jié)碼 jvm指令
public int add();
Code:
0: iconst_1 // 把1壓入操作數(shù)棧中
1: istore_1 //將int類型值存入局部變量1,這個(gè)局部變量1指局部變量表中的第一個(gè)數(shù)
2: iconst_2
3: istore_2
4: iload_1 //從局部變量1中裝載int類型值,這里的局部變量指第一個(gè)數(shù)贰军,即a
5: iload_2
6: iadd //執(zhí)行相加
7: istore_3 //存儲(chǔ)c
8: iload_3 //裝載c
9: ireturn //返回
}
實(shí)現(xiàn):
通過寄存器實(shí)現(xiàn)测蘑,把cup的寄存器當(dāng)做程序計(jì)數(shù)器
2.虛擬機(jī)棧
2.1定義
java Virtual Machine Stacks (java 虛擬機(jī)棧)
- 每個(gè)線程運(yùn)行時(shí)所需要的內(nèi)存,稱為虛擬機(jī)棧
- 每個(gè)棧有多個(gè)棧幀(Frame)組成尺栖,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占的內(nèi)存
- 每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法
問題:
- 垃圾回收是否涉及棧內(nèi)存烦租?方法調(diào)用完會(huì)自動(dòng)彈出延赌,回收內(nèi)存除盏,垃圾回收不涉及棧內(nèi)存
- 棧內(nèi)存分配越大越好?棧內(nèi)存劃得越大挫以,線程數(shù)會(huì)越少者蠕,因?yàn)閮?nèi)存有限,棧內(nèi)存是線程獨(dú)享
- 方法內(nèi)的局部變量是否線程安全掐松? 線程棧的線程私有的踱侣,是線程安全的
- 如果方法內(nèi)局部變量,沒有逃離方法的作用范圍大磺,它是線程安全的
- 如果是局部變量引用了對(duì)象抡句,并逃離方法的作用方法,需要考慮線程安全
/**
* 演示棧幀
*/
public class Demo1_1 {
public static void main(String[] args) throws InterruptedException {
method1();
}
private static void method1() {
method2(1, 2);
}
private static int method2(int a, int b) {
int c = a + b;
return c;
}
}
通過上面的代碼杠愧,設(shè)置斷點(diǎn)待榔,進(jìn)行debug,可以觀察method1以及method2的方法棧出入情況
2.2棧內(nèi)存溢出
- 棧幀過多流济,導(dǎo)致棧內(nèi)存溢出
- 棧幀過大究抓,導(dǎo)致棧內(nèi)存溢出
2.3線程運(yùn)行診斷
案例1: cpu占用過多
定位
Linux 的 nohup java 命令 可以后臺(tái)執(zhí)行java代碼
ps 命令可以 查看進(jìn)程與線程的對(duì)應(yīng)cpu占用情況
- 用top定位哪個(gè)進(jìn)程對(duì)cpu的占用過高
- ps H -eo pid,tid,%cpu |grep 進(jìn)程 id (用ps命令進(jìn)一步定位是哪個(gè)線程引起的cpu占用過高)
- jstack 進(jìn)程id (列出java線程)
- 可以根據(jù)線程id找到有問題的線程,進(jìn)一步定位到問題代碼的源碼行號(hào)(進(jìn)程號(hào)需要轉(zhuǎn)為16進(jìn)制進(jìn)行查找)
案例2:程序運(yùn)行很長(zhǎng)時(shí)間沒有結(jié)果
死鎖
使用 jstack 看死鎖
3.本地方法棧
本地方法棧發(fā)揮的作用與虛擬機(jī)棧的作用類似袭灯,用于native修飾的方法
4.堆
4.1 定義
Heap 堆
- 通過 new 關(guān)鍵字刺下,創(chuàng)建對(duì)象都會(huì)使用堆內(nèi)存
特點(diǎn)
- 它是線程共享的,堆中對(duì)象都需要考慮線程安全問題
- 有垃圾回收機(jī)制
4.2 堆內(nèi)存溢出
/**
* 演示堆內(nèi)存溢出 java.lang.OutOfMemoryError: Java heap space
* -Xmx8m
*/
public class Demo1_5 {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a); // hello, hellohello, hellohellohellohello ...
a = a + a; // hellohellohellohello
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
下圖可以調(diào)整堆內(nèi)存大小
4.3 堆內(nèi)存診斷
-
jps工具
- 查看當(dāng)前系統(tǒng)中有哪些java進(jìn)程
-
jmap工具
- 查看堆內(nèi)存占用情況 jmap
-
jconsole工具
- 圖形界面稽荧,多功能的監(jiān)測(cè)工具橘茉,可以連續(xù)監(jiān)測(cè)
4.jvisualvm(推薦使用)
package cn.itcast.jvm.t1.heap;
/**
* 演示堆內(nèi)存
*/
public class Demo1_4 {
public static void main(String[] args) throws InterruptedException {
System.out.println("1...");
Thread.sleep(30000);
byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
System.out.println("2...");
Thread.sleep(20000);
array = null;
System.gc();
System.out.println("3...");
Thread.sleep(1000000L);
}
}
打開cmd 輸入 jps命令查看當(dāng)前java線程
使用 jmap -heap 線程號(hào) 可以查看當(dāng)前線程的堆的使用情況
在代碼中的1,2,3步驟中分別執(zhí)行3次,可以得到3個(gè)結(jié)果
案例:
-
垃圾回收后姨丈,內(nèi)存占用仍然很高
/** * 演示查看對(duì)象個(gè)數(shù) 堆轉(zhuǎn)儲(chǔ) dump */ public class Demo1_13 { public static void main(String[] args) throws InterruptedException { List<Student> students = new ArrayList<>(); for (int i = 0; i < 200; i++) { students.add(new Student()); // Student student = new Student(); } Thread.sleep(1000000000L); } } class Student { private byte[] big = new byte[1024*1024]; }
使用 jvisualvm 命令進(jìn)行診斷
使用 堆Dunp 進(jìn)行對(duì)當(dāng)前線程內(nèi)存進(jìn)行轉(zhuǎn)儲(chǔ)快照畅卓,進(jìn)而分析為什么垃圾回收后內(nèi)存還很高
image
找出占用內(nèi)存最大的一個(gè)數(shù)組,查看
找到原來時(shí)數(shù)組里面對(duì)象太多蟋恬,占用了很多內(nèi)存
5.方法區(qū)
5.1 定義
5.2 組成
jdk 1.6 對(duì)方法區(qū)的實(shí)現(xiàn)稱為永久代
jdk 1.8 對(duì)方法區(qū)的實(shí)現(xiàn)稱為元空間
jdk1.6
jdk1.8以前翁潘,方法區(qū)是在jvm內(nèi)存上面,jdk1.8以后歼争,方法區(qū)是一個(gè)邏輯分區(qū)拜马,在計(jì)算機(jī)本地內(nèi)存上面
jdk1.8
5.3 方法區(qū)內(nèi)存溢出
?
public class Demo1_8 extends ClassLoader { //可以用來加載類的二進(jìn)制字節(jié)碼
public static void main(String[] args) {
int j = 0;
try {
Demo1_8 test = new Demo1_8();
for (int i = 0; i < 20000; i++, j++) {
//ClassWriter 作用是生成類的二進(jìn)制字節(jié)碼
ClassWriter cw = new ClassWriter(0);
//版本號(hào),pubblic沐绒,類名俩莽,包名,父類
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
//生成類乔遮,并且返回byte[]
byte[] code = cw.toByteArray();
//只會(huì)觸發(fā)類的加載扮超,不會(huì)觸發(fā)鏈接。。等
test.defineClass("Class" + i, code, 0, code.length);//class對(duì)象
}
} finally {
System.out.println(j);
}
}
}
-
1.8以前(1.6)會(huì)導(dǎo)致永久代內(nèi)存溢出
演示永久代內(nèi)存溢出 java.lang.OutOfMemoryError: PermGen space -XX:MaxPermSize=8m
-
1.8以后會(huì)導(dǎo)致元空間內(nèi)存溢出
演示元空間內(nèi)存溢出 java.lang.OutOfMemoryError: Metaspace -XX:MaxMetaspaceSize=8m
?
場(chǎng)景:框架會(huì)產(chǎn)生很多運(yùn)行時(shí)的類出刷,容易導(dǎo)致內(nèi)存溢出
spring
-
mybatis
都用到cglib
5.3 運(yùn)行時(shí)常量池
- 常量池璧疗,就是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名馁龟,方法名崩侠,參數(shù)類型,字面量等信息
- 運(yùn)行時(shí)常量池屁柏,常量池是*.class 文件中的啦膜,當(dāng)該類被加載有送,它的常量池信息就會(huì)放入運(yùn)行時(shí)常量池淌喻,并把里面的符號(hào)地址變?yōu)檎鎸?shí)地址
// 二進(jìn)制字節(jié)碼(類基本信息,常量池雀摘,類方法定義裸删,包含了虛擬機(jī)指令)
public class Demo1_22 {
public static void main(String[] args) {
String s1 = "a"; //懶惰
String s2 = "b";
String s3 = "ab";
}
執(zhí)行以下命令對(duì)代碼進(jìn)行反編譯
javap -v Demo1_22.class
Classfile /D:/IDEAworkplace/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.class
Last modified 2020-1-30; size 534 bytes
MD5 checksum 5c4213b2f1defff2bb24bf7cbd5ff183
Compiled from "Demo1_22.java"
public class cn.itcast.jvm.t1.stringtable.Demo1_22
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//常量池
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = String #25 // a
#3 = String #26 // b
#4 = String #27 // ab
#5 = Class #28 // cn/itcast/jvm/t1/stringtable/Demo1_22
#6 = Class #29 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcn/itcast/jvm/t1/stringtable/Demo1_22;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 s2
#21 = Utf8 s3
#22 = Utf8 SourceFile
#23 = Utf8 Demo1_22.java
#24 = NameAndType #7:#8 // "<init>":()V
#25 = Utf8 a
#26 = Utf8 b
#27 = Utf8 ab
#28 = Utf8 cn/itcast/jvm/t1/stringtable/Demo1_22
#29 = Utf8 java/lang/Object
{
public cn.itcast.jvm.t1.stringtable.Demo1_22();
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 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/itcast/jvm/t1/stringtable/Demo1_22;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
//執(zhí)行指令代碼
Code:
stack=1, locals=4, 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: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 6
line 26: 9
//局部變量表
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 s1 Ljava/lang/String;
6 4 2 s2 Ljava/lang/String;
9 1 3 s3 Ljava/lang/String;
}
SourceFile: "Demo1_22.java"
5.4 StringTable(串池)
StringTable 是運(yùn)行時(shí)常量池中的一個(gè)東西
// StringTable [ "a", "b" ,"ab" ] hashtable 結(jié)構(gòu)风喇,不能擴(kuò)容
public class Demo1_22 {
// 常量池中的信息贴铜,都會(huì)被加載到運(yùn)行時(shí)常量池中, 這時(shí) a b ab 都是常量池中的符號(hào)惯驼,還沒有變?yōu)?java 字符串對(duì)象
// ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
// ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
// ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象
public static void main(String[] args) {
String s1 = "a"; //懶惰
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
String s5 = "a" + "b"; // javac 在編譯期間的優(yōu)化清蚀,結(jié)果已經(jīng)在編譯期確定為ab
System.out.println(s3 == s5);
}
}
//結(jié)果
s4 不等于 s3 //s3是串池中的匕荸,s4是通過new對(duì)象生成的,其值存在堆中
s3 等于 s5 //
對(duì)于 單獨(dú)的賦值枷邪,是對(duì)數(shù)據(jù)的直接到StringTable中取找榛搔,如果沒有,則創(chuàng)建东揣,有則直接取
對(duì)于 s4 = s1+s2 則是使用StringBuilder(),方法進(jìn)行拼接践惑,結(jié)果是一個(gè)新的對(duì)象,該對(duì)象存在堆上面
使用 javap -v 命令后編譯結(jié)果如下
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: 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/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_3
37: aload 5
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
50: return
常量池內(nèi)容
Constant pool:
#1 = Methodref #12.#36 // java/lang/Object."<init>":()V
#2 = String #37 // a
#3 = String #38 // b
#4 = String #39 // ab
#5 = Class #40 // java/lang/StringBuilder
#6 = Methodref #5.#36 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #45.#46 // java/io/PrintStream.println:(Z)V
#11 = Class #47 // cn/itcast/jvm/t1/stringtable/Demo1_22
#12 = Class #48 // java/lang/Object
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lcn/itcast/jvm/t1/stringtable/Demo1_22;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 s1
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 s2
#27 = Utf8 s3
#28 = Utf8 s4
#29 = Utf8 s5
#30 = Utf8 StackMapTable
#31 = Class #23 // "[Ljava/lang/String;"
#32 = Class #49 // java/lang/String
#33 = Class #50 // java/io/PrintStream
#34 = Utf8 SourceFile
#35 = Utf8 Demo1_22.java
#36 = NameAndType #13:#14 // "<init>":()V
#37 = Utf8 a
#38 = Utf8 b
#39 = Utf8 ab
#40 = Utf8 java/lang/StringBuilder
#41 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#42 = NameAndType #53:#54 // toString:()Ljava/lang/String;
#43 = Class #55 // java/lang/System
#44 = NameAndType #56:#57 // out:Ljava/io/PrintStream;
#45 = Class #50 // java/io/PrintStream
#46 = NameAndType #58:#59 // println:(Z)V
#47 = Utf8 cn/itcast/jvm/t1/stringtable/Demo1_22
#48 = Utf8 java/lang/Object
#49 = Utf8 java/lang/String
#50 = Utf8 java/io/PrintStream
#51 = Utf8 append
#52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#53 = Utf8 toString
#54 = Utf8 ()Ljava/lang/String;
#55 = Utf8 java/lang/System
#56 = Utf8 out
#57 = Utf8 Ljava/io/PrintStream;
#58 = Utf8 println
#59 = Utf8 (Z)V
5.5 StringTable特性
- 常量池中的字符串僅是符號(hào)嘶卧,第一次用到時(shí)才變?yōu)閷?duì)象
- hashtable 結(jié)構(gòu)尔觉,不能擴(kuò)容
- 利用串池的機(jī)制,來避免重復(fù)創(chuàng)建字符串對(duì)象
- 字符串變量拼接的原理是 StringBuilder (1.8)
- 字符串常量拼接的原理是編譯期優(yōu)化
- 可以使用itern方法芥吟,主動(dòng)將串池中還沒有的字符串對(duì)象放入串池
- 1.8 將這個(gè)字符對(duì)象嘗試放入串池侦铜,如果有則并不會(huì)放入,如果沒有則放入串池钟鸵,會(huì)把串池中的對(duì)象返回
- 1.6 將這個(gè)字符對(duì)象嘗試放入串池泵额,如果有則并不會(huì)放入,如果沒有會(huì)把此對(duì)象復(fù)制一份携添,放入串池嫁盲,會(huì)把串池中的對(duì)象返回
先看幾道面試題:
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; //ab
Strin s4 = s1 + s2; //new String("ab")
String s5 = "ab";
String s6 = s4.intern();//常量池中以及有"ab"了,所以s4沒能入池成功
//問
System.out.println(s3 == s4); //false
System.out.println(s3 == s5); //true
System.out.println(s3 == s6); //true
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();//intern 方法在jdk1.6之前是不一樣的
//問,如果調(diào)換了【最后兩行代碼】的位置呢羞秤?如果jdk1.6呢缸托?
System.out.println(x1 == x2);//false
常量池與串池的關(guān)系:
常量池一開始是存在于字節(jié)碼中,當(dāng)運(yùn)行時(shí)瘾蛋,都會(huì)加載到運(yùn)行時(shí)常量池中俐镐,常量池中的信息都會(huì)被加載到運(yùn)行時(shí)常量池,此時(shí)常量池中的符號(hào)還不是java的字符串對(duì)象
StringTable 的字符串是延遲加載機(jī)制哺哼,即要使用才加載進(jìn)來佩抹,
常量池中的信息,都會(huì)被加載到運(yùn)行時(shí)常量池中取董, 這時(shí) a b ab 都是常量池中的符號(hào)棍苹,還沒有變?yōu)?java 字符串對(duì)象
ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象
// StringTable [ "a", "b" ,"ab" ] hashtable 結(jié)構(gòu),不能擴(kuò)容
public class Demo1_22 {
// 常量池中的信息茵汰,都會(huì)被加載到運(yùn)行時(shí)常量池中枢里, 這時(shí) a b ab 都是常量池中的符號(hào),還沒有變?yōu)?java 字符串對(duì)象
// ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
// ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
// ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象
public static void main(String[] args) {
String s1 = "a"; //懶惰
String s2 = "b";
String s3 = "ab";
}
//對(duì)應(yīng)jvm指令為
Code:
stack=1, locals=4, 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: return
LineNumberTable:
line 11: 0
line 12: 3
line 13: 6
line 26: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 s1 Ljava/lang/String;
6 4 2 s2 Ljava/lang/String;
9 1 3 s3 Ljava/lang/String;
}
從指令中可以看出 String s4 = s1 + s2蹂午,是將s1以及s2的值使用StringBuilder進(jìn)行拼接栏豺,最后調(diào)用StringBuilder的toString方法進(jìn)行重新生成一個(gè)新的對(duì)象( 等于new StringBuilder().append("a").append("b").toString() new String("ab"))的底層是通過StringBuilder進(jìn)行字符串的拼接生成字符串對(duì)象,存儲(chǔ)在堆內(nèi)存中 豆胸,所以s4 不等于 s3
String s4 = s1 + s2;
//這一行代碼的jvm指令為
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/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
//查看StringBuilder 的 toString方法奥洼,看出來是生成一個(gè)新的對(duì)象
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
String s5 = "a" + "b"; (javac在編譯初期的優(yōu)化,就已經(jīng)確定為ab了晚胡,所以jvm指令直接讀取ab,并生成對(duì)象)所以 s5 等于 s3
String s5 = "a" + "b";
// javac 在編譯期間的優(yōu)化灵奖,結(jié)果已經(jīng)在編譯期確定為ab
//這一行代碼的jvm指令為
29: ldc #4 // String ab
31: astore 5
StringTable 字符串延遲加載
在代碼中設(shè)置多個(gè)斷點(diǎn),觀察StringTable的字符數(shù)量變化搬泥,可以看出字符串是具有延遲加載機(jī)制的
在idea中debug模式中的Memory中可以查看串池中字符的個(gè)數(shù)
intern方法jdk 1.6 與 1.8區(qū)別
- 1.8 將這個(gè)字符對(duì)象嘗試放入串池桑寨,如果有則并不會(huì)放入,如果沒有則放入串池忿檩,會(huì)把串池中的對(duì)象返回
- 1.6 將這個(gè)字符對(duì)象嘗試放入串池尉尾,如果有則并不會(huì)放入,如果沒有會(huì)把此對(duì)象復(fù)制一份燥透,放入串池沙咏,會(huì)把串池中的對(duì)象返回
intern :
最初為空的字符串池由String
類String
。
當(dāng)調(diào)用intern方法時(shí)班套,如果池已經(jīng)包含與equals(Object)
方法確定的相當(dāng)于此String
對(duì)象的字符串肢藐,則返回來自池的字符串。 否則吱韭,此String
對(duì)象將添加到池中吆豹,并返回對(duì)此String
對(duì)象的引用。
由此可見,對(duì)于任何兩個(gè)字符串s
和t
痘煤, s.intern() == t.intern()
是true
當(dāng)且僅當(dāng)s.equals(t)
是true
凑阶。
將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入衷快,如果沒有則放入串池宙橱, 會(huì)把串池中的對(duì)象返回
1.8
// ["a", "b", "ab"]
public static void main(String[] args) {
String s = new String("a") + new String("b"); //new String("ab")
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入蘸拔,如果沒有則放入串池师郑, 會(huì)把串池中的對(duì)象返回
String x = "ab";
System.out.println(s2 == x);
System.out.println(s == x);
}
}
//輸出
//true
//true
// ["ab", "a", "b"]
public static void main(String[] args) {
String x = "ab";
String s = new String("a") + new String("b"); //new String("ab")
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入调窍,如果沒有則放入串池宝冕, 會(huì)把串池中的對(duì)象返回
//由于此時(shí),"ab"已經(jīng)存在陨晶,直接返回"ab",s沒能入池成功
System.out.println( s2 == x);
System.out.println( s == x ); //此時(shí) s 是在堆中的對(duì)象
}
}
//輸出
//true
//false
jdk1.6
// ["ab", "a", "b"]
public static void main(String[] args) {
String x = "ab";
String s = new String("a") + new String("b"); //new String("ab")
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 將這個(gè)字符串對(duì)象嘗試放入串池猬仁,如果有則并不會(huì)放入帝璧,如果沒有則放入串池先誉, 會(huì)把串池中的對(duì)象返回
// s 拷貝一份,放入串池
// System.out.println(s2 == "ab");
// System.out.println(s == "ab");
System.out.println( s2 == x);
System.out.println( s == x );
}
}
//輸出
//true
//false
// [ "a", "b","ab"]
public static void main(String[] args) {
String s = new String("a") + new String("b"); //new String("ab")
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 將這個(gè)字符串對(duì)象嘗試放入串池的烁,如果有則并不會(huì)放入褐耳,如果沒有則放入串池, 會(huì)把串池中的對(duì)象返回
// s 拷貝一份渴庆,放入串池
// System.out.println(s2 == "ab");
// System.out.println(s == "ab");
String x = "ab";
System.out.println( s2 == x);
System.out.println( s == x ); //s依然是堆里面的值铃芦,不一樣
}
}
//輸出
//true
//false
5.6 StringTable位置
- 7 的時(shí)候StringTable 是在堆空間
1.6 時(shí)StringTable是在永久代中 ,永久代是Full GC (老年代空間不足才會(huì)觸發(fā))才會(huì)觸發(fā)襟雷,導(dǎo)致StringTable的回收效率不高刃滓,所以在1.7 以后把StringTable 轉(zhuǎn)移到堆中(只需要miner GC 就能觸發(fā)垃圾回收)
/**
* 演示 StringTable 位置
* 在jdk8下設(shè)置 -Xmx10m -XX:-UseGCOverheadLimit
* 在jdk6下設(shè)置 -XX:MaxPermSize=10m
*/
public class Demo1_6 {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
int i = 0;
try {
for (int j = 0; j < 260000; j++) {
list.add(String.valueOf(j).intern());//把產(chǎn)生的數(shù)字變成字符,然后加入串池中
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
}
jdk 1.8
java.lang.OutOfMemoryError: Java heap space
堆空間不足(StringTable 在堆中)
jdk 1.6
永久代空間不足(StringTable在永久代中)
java.lang.OutOfMemoryError: PermGen space
5.7 StringTable 垃圾回收
/**
* 演示 StringTable 垃圾回收
* -XX:MaxPermSize=10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
*/
public class Demo1_7 {
// 1754
public static void main(String[] args) throws InterruptedException {
int i = 0;
try {
for (int j = 0; j < 500000; j++) { // j=10, j=1000000
String.valueOf(j).intern();
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
}
5.8 StringTable 性能調(diào)優(yōu)
- 調(diào)整 -XX:StringTableSize=桶個(gè)數(shù)
- 考慮將字符串對(duì)象是否入池
? 如果字符常量比較多時(shí)耸弄,可以把桶的個(gè)數(shù)(StringTableSize)調(diào)大咧虎,讓有更多的hash,減少hash沖突
/**
* 演示 intern 減少內(nèi)存占用
* -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics
* -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000
*/
public class Demo1_25 {
public static void main(String[] args) throws IOException {
List<String> address = new ArrayList<>();
System.in.read();
for (int i = 0; i < 10; i++) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {
String line = null;
long start = System.nanoTime();
while (true) {
line = reader.readLine();
if(line == null) {
break;
}
address.add(line.intern());
}
System.out.println("cost:" +(System.nanoTime()-start)/1000000);
}
}
System.in.read();
}
}
6.直接內(nèi)存
6.1 定義
常見于NIO操作時(shí),用于數(shù)據(jù)緩沖區(qū)
分配回收成本較高计呈,但讀寫性能高
-
不受JVM內(nèi)存回收管理
傳統(tǒng)io:
image
ByteBuffer:
/**
* 演示 ByteBuffer 作用
*/
public class Demo1_9 {
static final String FROM = "E:\\編程資料\\第三方教學(xué)視頻\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4";
static final String TO = "E:\\a.mp4";
static final int _1Mb = 1024 * 1024;
public static void main(String[] args) {
io(); // io 用時(shí):1535.586957 1766.963399 1359.240226
directBuffer(); // directBuffer 用時(shí):479.295165 702.291454 562.56592
}
private static void directBuffer() {
long start = System.nanoTime();
try (FileChannel from = new FileInputStream(FROM).getChannel();
FileChannel to = new FileOutputStream(TO).getChannel();
) {
ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
while (true) {
int len = from.read(bb);
if (len == -1) {
break;
}
bb.flip();
to.write(bb);
bb.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("directBuffer 用時(shí):" + (end - start) / 1000_000.0);
}
private static void io() {
long start = System.nanoTime();
try (FileInputStream from = new FileInputStream(FROM);
FileOutputStream to = new FileOutputStream(TO);
) {
byte[] buf = new byte[_1Mb];
while (true) {
int len = from.read(buf);
if (len == -1) {
break;
}
to.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("io 用時(shí):" + (end - start) / 1000_000.0);
}
}
6.2 分配和回收原理
使用了Unsafe 對(duì)象完成直接內(nèi)存的分配回收砰诵,并且回收需要主動(dòng)調(diào)用freeMemory方法
ByteBuffer 的實(shí)現(xiàn)類內(nèi)部,使用了 Cleaner(虛引用) 來監(jiān)測(cè) ByteBuffer 對(duì)象捌显,一旦 ByteBuffer 對(duì)象被垃圾回收茁彭,那么就會(huì)由 ReferenceHandler 線程通過 Cleaner 的 clean 方法調(diào)用 freeMenory 來釋放直接內(nèi)存