From:深入理解Java虛擬機(jī)
- 目錄
BiBi - JVM -0- 開篇
BiBi - JVM -1- Java內(nèi)存區(qū)域
BiBi - JVM -2- 對象
BiBi - JVM -3- 垃圾收集算法
BiBi - JVM -4- HotSpot JVM
BiBi - JVM -5- 垃圾回收器
BiBi - JVM -6- 回收策略
BiBi - JVM -7- Java類文件結(jié)構(gòu)
BiBi - JVM -8- 類加載機(jī)制
BiBi - JVM -9- 類加載器
BiBi - JVM -10- 虛擬機(jī)字節(jié)碼
BiBi - JVM -11- 編譯期優(yōu)化
BiBi - JVM -12- 運行期優(yōu)化
BiBi - JVM -13- 并發(fā)
編譯期的三種情況
1)前端編譯器:把java文件轉(zhuǎn)變?yōu)閏lass文件。
2)JIT編譯器:運行期編譯器把字節(jié)碼轉(zhuǎn)變?yōu)闄C(jī)器碼弃榨。
3)AOT編譯器:把java文件編譯成本地機(jī)器碼掉瞳。
對性能的優(yōu)化主要集中在JIT,這樣可以讓那些不是由javac產(chǎn)生的class文件【如:Groovy淫茵、JRuby】也同樣能享受到編譯器優(yōu)化帶來的好處。
javac編譯的過程
(解析與填充符號表 >< 注解處理) > 分析與字節(jié)碼生成
public void foo(final int arg) {
final int var = 0;
}
public void foo(int arg) {
int var = 0;
}
上面兩段代碼編譯出來的Class文件是一樣的。由于局部變量在常量池中沒有CONSTANT_Fieldref_info的符號引用碉克,自然就沒有訪問標(biāo)志的信息,所以在Class文件中不可能知道一個局部變量是不是聲明為final赁还。
將局部變量聲明為final妖泄,對運行期是沒有影響的,變量的不變性僅僅由編譯器在編譯期間保障艘策。
語法糖
泛型蹈胡、變長參數(shù)、自動裝箱/拆箱朋蔫、高級for循環(huán)罚渐、內(nèi)部類、枚舉類驯妄、switch支持字符串等荷并,虛擬機(jī)運行時不支持這些語法,它們在編譯階段還原回基礎(chǔ)的語法結(jié)構(gòu)青扔。
如果用戶代碼中沒有提供任何構(gòu)造函數(shù)源织,那編譯器將會添加一個沒有參數(shù)的、訪問性與當(dāng)前類一致的默認(rèn)構(gòu)造函數(shù)微猖,這個工作在【填充符號表】階段完成雀鹃。
把字符串的加操作替換為StringBuffer或StringBuilder的append操作。
void say() {
String str = "bb" + "cc" + "dd" + "ee";
str = "aa" + str;
System.out.println(str);
}
對應(yīng)的Class為:
void say() {
String var1 = "bbccddee";
var1 = "aa" + var1;
System.out.println(var1);
}
void say() {
String[] arr = { "aa", "bb", "cc" };
String res = null;
for (String str : arr) {
res += str;
}
System.out.println(res);
}
對用的Class為:
void say() {
String[] var1 = new String[]{"aa", "bb", "cc"};
String var2 = null;
String[] var3 = var1;
int var4 = var1.length;
for(int var5 = 0; var5 < var4; ++var5) {
String var6 = var3[var5];
var2 = var2 + var6;
}
System.out.println(var2);
}
void say() {
List<String> list = new ArrayList<>();
list.add("ljg");
for (String s : list) {
System.out.println(s);
}
}
對用的Class為:
void say() {
ArrayList var1 = new ArrayList();
var1.add("ljg");
Iterator var2 = var1.iterator();
while(var2.hasNext()) {
String var3 = (String)var2.next();
System.out.println(var3);
}
}
總結(jié):
1)字符串相加優(yōu)化的場景是有限的励两,所以在開發(fā)中還是要自己主動使用StringBuilder黎茎。
2)高級for循環(huán)會被轉(zhuǎn)化為基本for循環(huán)樣式。
3)泛型會被擦除当悔,轉(zhuǎn)換為手動的類型轉(zhuǎn)換
真泛型:C#傅瞻,在運行期是真實存在的,有自己的虛方法表和類型數(shù)據(jù)盲憎。
偽泛型:Java嗅骄,在編譯后的字節(jié)碼中會擦除泛型信息,替換為原生類型饼疙,并在相應(yīng)的地方插入強制類型轉(zhuǎn)換溺森。對Java而言,ArrayList<int>和ArrayList<String>是同一個類窑眯。
public void method(List<String> list) {
System.out.println(list.toString());
}
public void method(List<Integer> list) {
System.out.println(list.toString());
}
上面的代碼不能被編譯屏积,因為擦除后兩個方法簽名一樣。
- Integer
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);//ture
System.out.println(e == f);//falde
System.out.println(c == (a + b));//true
System.out.println(c.equals(a + b));//true
System.out.println(g == (a + b));//true
System.out.println(g.equals(a + b));//false 因為equals()方法不處理數(shù)據(jù)類型轉(zhuǎn)換
}
- switch字符串的語法糖
public static void main(String[] args) {
String str = "a";
switch (str) {
case "a":
System.out.println("aaa");
break;
case "b":
System.out.println("bbb");
break;
default:
break;
}
}
對應(yīng)的Class為:
public static void main(String[] var0) {
String var1 = "a";
byte var3 = -1;
switch(var1.hashCode()) {
case 97:
if (var1.equals("a")) {
var3 = 0;
}
break;
case 98:
if (var1.equals("b")) {
var3 = 1;
}
}
switch(var3) {
case 0:
System.out.println("aaa");
break;
case 1:
System.out.println("bbb");
}
}