前言
前兩天和朋友探討技術(shù)的時(shí)候有聊到JVM和JDK這一塊,聊到這里兩個(gè)人就像高山流水遇知音那是根本停不下來,事后我想著趁現(xiàn)在印象還比較深刻就把這些東西整理起來分享給大家來幫助更多的人吧。話不多說,滿滿的干貨都整理在下面了!
JVM探究
jvm的位置
jvm的體系結(jié)構(gòu)
堆里面有垃圾,需要被GC回收
棧里面是沒有垃圾的,用完就彈出去了,棧里面有垃圾,程序就崩了,執(zhí)行不完main方法。
Java棧,本地方法棧,程序計(jì)數(shù)器里面是不可能存在垃圾的。也就不會(huì)有垃圾回收。
所謂的jvm調(diào)優(yōu)就是在堆里面調(diào)優(yōu)了,jvm調(diào)優(yōu)99%都是在方法區(qū)和堆里面進(jìn)行調(diào)優(yōu)的。
類加載器
public class Car {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
Class<? extends Car> aClass1 = car1.getClass();
Class<? extends Car> aClass2 = car2.getClass();
Class<? extends Car> aClass3 = car3.getClass();
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass3.hashCode());
}
}
作用:加載class文件 - 類似new Student();
類是一個(gè)模板,是抽象的,而new出來的對(duì)象是具體的,是對(duì)這個(gè)抽象的類的實(shí)例化
1.虛擬機(jī)自帶的加載器
2.啟動(dòng)類(根)加載器
3.擴(kuò)展加載器
4.應(yīng)用程序(系統(tǒng)類)加載器
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader);//AppClassLoader 應(yīng)用程序加載器
System.out.println(classLoader.getParent());//ExtClassLoader 擴(kuò)展類加載器
System.out.println(classLoader.getParent().getParent());//null 1.不存在 2.Java程序獲取不到
1.類加載器收到類加載的請(qǐng)求
2.將這個(gè)請(qǐng)求向上委托給父類加載器去完成,一直向上委托,直到根加載器
3.啟動(dòng)類加載器會(huì)檢查是否能夠加載當(dāng)前這個(gè)類,能加載就結(jié)束,使用當(dāng)前加載器,否則,拋出異常,通知子類加載器進(jìn)行加載。
4.重復(fù)步驟3
若都找不到就會(huì)報(bào) Class Not Found
null:Java調(diào)用不到,可能編程語言是C寫的,所以調(diào)不到
Java =C+±- 去掉C里面比較繁瑣的東西 指針,內(nèi)存管理(JVM幫你做了)
雙親委派機(jī)制
雙親委派機(jī)制:安全
APP–>EXC–BOOTStrap(根目錄,最終執(zhí)行)
當(dāng)某個(gè)類加載器需要加載某個(gè).class文件時(shí),它首先把這個(gè)任務(wù)委托給他的上級(jí)類加載器,遞歸這個(gè)操作,如果上級(jí)的類加載器沒有加載,自己才會(huì)去加載這個(gè)類。
在src下創(chuàng)建Java.lang包,創(chuàng)建一個(gè)String類
package java.lang;
public class String {
public String toString(){
return "hello";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.getClass().getClassLoader());
s.toString();
}
}
執(zhí)行結(jié)果
它會(huì)去最終的BOOTStrap里面的String類里面去執(zhí)行,找到執(zhí)行類的位置,發(fā)現(xiàn)里面沒有要執(zhí)行的mian方法,所以會(huì)報(bào)這個(gè)錯(cuò)。
在src下創(chuàng)建類Student
public class Student {
public String toString(){
return "HELLO";
}
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.getClass().getClassLoader());
System.out.println(student.toString());
}
}
執(zhí)行結(jié)果
如上圖可見最終是在APP里面執(zhí)行的,成功輸出HELLO語句
雙親委派機(jī)制的作用
1、防止重復(fù)加載同一個(gè).class。通過委托去向上面問一問,加載過了,就不用再加載一遍。保證數(shù)據(jù)安全。
2、保證核心.class不能被篡改。通過委托方式,不會(huì)去篡改核心.class,即使篡改也不會(huì)去加載,即使加載也不會(huì)是同一個(gè).class對(duì)象了。不同的加載器加載同一個(gè).class也不是同一個(gè)Class對(duì)象。這樣保證了Class執(zhí)行安全。
沙箱安全機(jī)制
? Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一個(gè)限制程序運(yùn)行的環(huán)境,沙箱機(jī)制就是將Java代碼限定在虛擬機(jī)(JVM)特定的運(yùn)行范圍中,并且嚴(yán)格限制代碼對(duì)本地系統(tǒng)資源訪問,通過這樣的措施來保證對(duì)代碼的有效隔離,防止對(duì)本地系統(tǒng)造成破壞,沙箱主要限制系統(tǒng)資源訪問,那系統(tǒng)資源包括什么?CPU,內(nèi)存,文件系統(tǒng),網(wǎng)格,不同級(jí)別的沙箱對(duì)這些資源訪問的限制也是可以不一樣。
? 所以的Java程序運(yùn)行都可以指定沙箱,可以定制安全策略。
? 在Java中將執(zhí)行程序分為本地代碼呵遠(yuǎn)程代碼兩種,本地代碼默認(rèn)視為可信任的,而遠(yuǎn)程代碼則被看作是不受信任的,對(duì)于授權(quán)的本地代碼,可以訪問一切本地資源,而對(duì)于非授信的遠(yuǎn)程代碼在早期的Java實(shí)現(xiàn)中,安全依賴于沙箱機(jī)制,如下圖jdk1.0安全模型
但如此嚴(yán)格的安全機(jī)制也給程序的功能擴(kuò)展帶來障礙,比如當(dāng)用戶希望遠(yuǎn)程代碼訪問本地系統(tǒng)的文件時(shí)候,就無法實(shí)現(xiàn),因此在后續(xù)的Java1.1版本中,針對(duì)安全機(jī)制做了改進(jìn),增加了安全策略,允許用戶指定代碼本地資源的訪問權(quán)限,如下圖所示JDK1.1安全模型
? 在Java1.2版本中,再次改進(jìn)了安全機(jī)制,增加了代碼簽名,不論本地代碼或是遠(yuǎn)程代碼,都會(huì)按照用戶的安全策略設(shè)定,由類加載器加載到虛擬機(jī)中權(quán)限不同的運(yùn)行空間,來實(shí)現(xiàn)差異化的代碼執(zhí)行權(quán)限控制,如下圖所JDK1.2安全模型
當(dāng)前最新的安全機(jī)制實(shí)現(xiàn),則引入域(Domain)的概念,虛擬機(jī)會(huì)把所有代碼加載到不同的系統(tǒng)域和應(yīng)用域,系統(tǒng)域部分專門負(fù)責(zé)與關(guān)鍵資源進(jìn)行交互,而各個(gè)應(yīng)用域部分則通過系統(tǒng)域的部分代理來各種需求的資源進(jìn)行訪問,虛擬機(jī)中不同的受保護(hù)域,對(duì)應(yīng)不一樣的權(quán)限,存在不同域中的類文件就具有了當(dāng)前域的全部權(quán)限,如下圖所示,最新的安全模型
組成沙箱的基本組件:
字節(jié)碼校驗(yàn)器(bytecode verifier):確保Java類文件遵循Java語言規(guī)范,這樣可以幫助Java程序?qū)崿F(xiàn)內(nèi)存保護(hù),但并不是所有的類文件都會(huì)經(jīng)過字節(jié)碼校驗(yàn),比如核心類。
類加載器(class loader):其中類加載器在3個(gè)方面對(duì)Java沙箱起作用
? 它防止惡意代碼去干涉善意的代碼;//雙親委派機(jī)制
? 它守護(hù)了被信任的類庫邊界;
? 它將代碼歸入保護(hù)域,確定了代碼可以進(jìn)行哪些操作;
? 虛擬機(jī)為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個(gè)被裝載的類將有一個(gè)名字,這個(gè)命名空間由Java虛擬機(jī)為每一個(gè)類加載器維護(hù)的,它們互相甚至不可見。
? 類加載器采用的機(jī)制是雙親委派模式。
? 虛擬機(jī)為不同的類加載開始加載,外層惡意同名類得不到加載從而無法使用;
? 由于嚴(yán)格通過包來區(qū)分了訪問域:外層惡意的類通過內(nèi)置代碼也無法獲得權(quán)限訪問到內(nèi)置類,破壞代碼就自然無法生效。
? 存取控制器(access controller):存取控制器可以控制核心API對(duì)操作系統(tǒng)的存取權(quán)限,而這個(gè)控制的策略設(shè)定,可以由用戶指定。
? 安全管理器 (security manager):是核心API和操作系統(tǒng)之間的主要接口,實(shí)現(xiàn)權(quán)限控制,比存取控制器優(yōu)先級(jí)高。
? 安全軟件包(security package):java.security下的類和擴(kuò)展包下的類,允許用戶為自己的應(yīng)用增加新的安全特性,包括:
? 安全提供者
? 信息摘要
? 數(shù)字簽名 kettools https(需要證書)
? 加密
? 鑒別
Native
凡是帶了native關(guān)鍵字,說明Java的的作用范圍達(dá)不到了,會(huì)去調(diào)用底層C語言的庫!
會(huì)進(jìn)入本地方法棧
調(diào)用本地方法本地接口 JNI
JNI的作用:擴(kuò)展Java的使用,融合不同的編程語言為Java所用,最初:C,C++
Java誕生的時(shí)候C ,C++比較火,Java想要立足,必須要有調(diào)用C,C++的程序。
他在內(nèi)存區(qū)域?qū)iT開辟了一塊標(biāo)記區(qū)域 :Native Method Stack,登記native方法
在最終執(zhí)行的時(shí)候,加載本地方法庫中的方法通過JNI
Java程序驅(qū)動(dòng)打印機(jī),管理系統(tǒng),掌握即可,在企業(yè)級(jí)應(yīng)用中較為少見。
? 目前該方法使用使用的越來越少了,除非 是與硬件有關(guān)的應(yīng)用,比如通過Java程序驅(qū)動(dòng)打印機(jī)或者Java系統(tǒng)管理生產(chǎn)設(shè)備,在企業(yè)級(jí)應(yīng)用中已經(jīng)比較少見,因?yàn)楝F(xiàn)在的異構(gòu)領(lǐng)域間通信很發(fā)達(dá),比如可以使用Socjet通信,也可以使Web Service等等 ,不多做介紹!
PC寄存器
? 程序計(jì)數(shù)器:Program Counter Register
? 每個(gè)線程都有一個(gè)程序計(jì)數(shù)器,要線程私有的,就是一個(gè)指針,指向方法區(qū)中的方法字節(jié)碼(用來存儲(chǔ)指向像一條指令的地址,也即將要執(zhí)行的指令代碼),在執(zhí)行引擎讀取下一條指令,是一個(gè)非常小的內(nèi)存空間,幾乎可以忽略不計(jì)。
方法區(qū)
Method Area 方法區(qū)
? 方法區(qū)是被所有線程共享,所有字段和方法字節(jié)碼,以及一些特殊方法,如構(gòu)造函數(shù),接口代碼也在此定義,簡單說,所有定義的方法的信息都保存在該區(qū)域,在此區(qū)域?qū)儆诠蚕韰^(qū)間
? 靜態(tài)變量,常量,類信息(構(gòu)造方法,接口定義),運(yùn)行時(shí)的常量池存在方法區(qū)中,但是實(shí)例變量存在堆內(nèi)存中,與方法區(qū)無關(guān)。
static,final,Class,常量池
棧
棧是一種數(shù)據(jù)結(jié)構(gòu)
? 程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法 :持續(xù)學(xué)習(xí)~
? 程序 = 框架 + 業(yè)務(wù)邏輯 :吃飯~
棧:先進(jìn)后出,后進(jìn)先出
隊(duì)列:先進(jìn)先出(FIFO:First input First Output)
方法運(yùn)行完成以后,就會(huì)被棧彈出去
兩個(gè)方法互相調(diào),就會(huì)導(dǎo)致棧溢出
public class Inn {
public static void main(String[] args) {
new Inn().test();
}
public void test(){
a();
}
public void a(){
test();
}
//a調(diào)test,test調(diào)a
}
運(yùn)行結(jié)果
棧:棧內(nèi)存,主管程序的運(yùn)行,生命周期和線程同步,也就是線程如果都結(jié)束了,棧也就變成空的了;
線程結(jié)束,占內(nèi)存也就釋放了,對(duì)于棧來說,不存在垃圾回收問題,一旦線程結(jié)束,棧就沒了。
棧:8大基本類型+對(duì)象引用+實(shí)例的方法
棧運(yùn)行原理:棧幀
函數(shù)調(diào)用過程中,肯定需要空間的開辟,而調(diào)用這個(gè)函數(shù)時(shí)為該函數(shù)開辟的空間就叫做該函數(shù)的棧幀
程序正在執(zhí)行的方法一定在棧的頂部,執(zhí)行完就會(huì)彈出去
棧1在運(yùn)行完成之后就會(huì)彈出去,然后棧2在去執(zhí)行,棧2執(zhí)行完,程序也就結(jié)束了
棧滿了:StackOverflowError
棧+堆+方法區(qū) :交互
如下圖:對(duì)象在內(nèi)存中實(shí)例化的過程
三種jvm
Sun 公司 HotSpot Java HotSpot? 64-Bit Server VM (build 25.77-b03, mixed mode)
BEA :JRockit
IBM :J9 VM
我們用的是hotspot
堆
Heap(堆):一個(gè)jvm只有一個(gè),堆內(nèi)存的大小是可以調(diào)節(jié)的。
類加載器讀取了類文件后,一般會(huì)把什么東西放到堆中呢?類,方法,常量,保存我們所有引用類型的真實(shí)對(duì)象。
堆內(nèi)存中還要細(xì)分為三個(gè)區(qū)域:
新生區(qū)(伊甸園區(qū))
養(yǎng)老區(qū)
永久區(qū)
GC垃圾回收,主要是在伊甸園區(qū)和養(yǎng)老區(qū),
假設(shè)內(nèi)存滿了,會(huì)報(bào)OOM,堆內(nèi)存不夠,堆溢出
在jdk8以后,永久存儲(chǔ)區(qū)改了個(gè)名字叫(元空間)
新生區(qū)
它是一個(gè)類:誕生和成長的地方,甚至死亡;
新生區(qū)分為 伊甸園區(qū)和幸存者區(qū)
伊甸園區(qū);所有的對(duì)象都是在伊甸園區(qū)里new出來的
幸存區(qū):(0,1)
當(dāng)伊甸園區(qū)滿了以后,會(huì)觸發(fā)輕GC,對(duì)伊甸園區(qū)進(jìn)行垃圾回收,當(dāng)某個(gè)對(duì)象通過GC幸存下來以后,就會(huì)進(jìn)入到幸存者區(qū),依次不斷的循環(huán),當(dāng)幸存0區(qū)和1區(qū)也滿了的時(shí)候,在經(jīng)歷過多次GC以后,活下來的對(duì)象,也就是被重新引用的對(duì)象就會(huì)進(jìn)入到老年區(qū)。而當(dāng)老年區(qū)也滿了的時(shí)候,就會(huì)觸發(fā)重GC,重CG除了會(huì)去清理老年區(qū)的,還會(huì)伊甸園區(qū)和幸存0區(qū)1區(qū)的所有垃圾全清理掉。而在重GC清除下,活下來的就會(huì)進(jìn)入到養(yǎng)老區(qū)。當(dāng)重GC清理完畢以后,新生區(qū)和養(yǎng)老區(qū)還是都滿了,這個(gè)時(shí)候就會(huì)報(bào)堆溢出的報(bào)錯(cuò)。
真理:經(jīng)過研究,99%對(duì)象都是臨時(shí)對(duì)象。
老年區(qū)
新生區(qū)里面GC不掉的對(duì)象就會(huì)去到老年區(qū)
永久區(qū)
這個(gè)區(qū)域常駐內(nèi)存的,用來存放JDK自身攜帶的Class對(duì)象,Interface元數(shù)據(jù),存儲(chǔ)的是Java運(yùn)行時(shí)的一些環(huán)境或類信息,這個(gè)區(qū)域不存在垃圾回收!關(guān)閉VM虛擬機(jī)就會(huì)釋放這個(gè)區(qū)域的內(nèi)存。
一個(gè)啟動(dòng)類加載了大量的第三方j(luò)ar包,Tomcat部署了太多的應(yīng)用,大量動(dòng)態(tài)生成的反射類,不斷的被加載,直到內(nèi)存滿,就會(huì)出現(xiàn)OOM;
jdk1.6之前:永久代,常量池是在方法區(qū)之中
jdk1.7:永久代,但是慢慢的退化,去永久代,常量池在堆中
jdk1.8之后:無永久代,常量池在元空間。
邏輯上存在,物理上不存在
堆內(nèi)存調(diào)優(yōu)
堆內(nèi)存滿了,該如何處理?
public static void main(String[] args) {
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
System.out.println("max="+max+"字節(jié)\t"+(max/(double)1024/1024)+"MB");
System.out.println("total="+max+"字節(jié)\t"+(total/(double)1024/1024)+"MB");
}
? 先嘗試把堆內(nèi)存空間擴(kuò)大,假如還是用原來的代碼跑,繼續(xù)包堆溢出的錯(cuò),我們就該去考慮考慮自己代碼那塊有問題,可能是有垃圾代碼或者是死循環(huán)代碼,它在不斷的占用內(nèi)存空間
? 分析內(nèi)存,看一下那個(gè)地方出現(xiàn)了問題(專業(yè)工具)
? 調(diào)優(yōu)代碼-Xms1024m -Xmx1024m -XX:+PrintGCDetails
Xms后面填計(jì)算機(jī)給jvm分配的內(nèi)存,Xmx后面填Jvm初始的內(nèi)存值
在一個(gè)項(xiàng)目中,突然出現(xiàn)了OOM故障,那該如何排除研究為什么出錯(cuò)~
能夠看到代碼第幾行出錯(cuò);
Dubug:一行行分析代碼!
MAT,Jprofiler作用:
分析Dump內(nèi)存文件,快速定位內(nèi)存泄漏;
獲得堆中的數(shù)據(jù)
獲得大的對(duì)象~
。。。。
GC
jvm在進(jìn)行垃圾回收時(shí),并不是對(duì)這三個(gè)區(qū)域統(tǒng)一回收,大部分時(shí)候,回收但是新生代
新生代
幸存區(qū)(from,to)
老年區(qū)
GC兩種類:輕GC(普通的GC),重GC(全局GC)
GC常用算法
標(biāo)記清除法
? 最基礎(chǔ)的收集算法是“標(biāo)記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標(biāo)記”和“清除”兩個(gè)階段:
? 首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象,之所以說它是最基礎(chǔ)的收集算法,是因?yàn)楹罄m(xù)的收集算法都是基于這種思路并對(duì)其缺點(diǎn)進(jìn)行改進(jìn)而得到的。
? 它的主要缺點(diǎn)有兩個(gè):一個(gè)是效率問題,標(biāo)記和清除過程的效率都不高;另外一個(gè)是空間問題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致,當(dāng)程序在以后的運(yùn)行過程中需要分配較大對(duì)象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。標(biāo)記-清除算法的執(zhí)行過程(需要較大內(nèi)存時(shí)卻不夠了就要回收一次)
復(fù)制算法
為了解決效率問題,一種稱為“復(fù)制”(Copying)的收集算法出現(xiàn)了,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對(duì)其中的一塊進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單,運(yùn)行高效。只是這種算法的代價(jià)是將內(nèi)存縮小為原來的一半,未免太高了一點(diǎn)。
標(biāo)記-整理算法
復(fù)制收集算法在對(duì)象存活率較高時(shí)就要執(zhí)行較多的復(fù)制操作,效率將會(huì)變低。更關(guān)鍵的是,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
分代收集算法(并不是一種新的思想,只是將java堆分成新生代和老年代,根據(jù)各自特點(diǎn)采用不同算法)
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什么新的思想,只是根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āT谛律校看卫占瘯r(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。而老年代中因?yàn)閷?duì)象存活率高、沒有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法來進(jìn)行回收。
新生代–復(fù)制算法。老年代–標(biāo)記-整理算法。
字節(jié)碼引擎
1.概述
? Java虛擬機(jī)字節(jié)碼執(zhí)行引擎是jvm最核心的組成部分之一,所有的 Java 虛擬機(jī)的執(zhí)行引擎都是一致的:輸入的是字節(jié)碼文件,處理過程是字節(jié)碼解析的等效過程,輸出的是執(zhí)行結(jié)果,下面將主要從概念模型的角度來講解虛擬機(jī)的方法調(diào)用和字節(jié)碼執(zhí)行。
2.執(zhí)行引擎的解釋和作用
? 類加載器將字節(jié)碼載入內(nèi)存之后,執(zhí)行引擎以Java 字節(jié)碼指令為單元,讀取Java字節(jié)碼。問題是,現(xiàn)在的java字節(jié)碼機(jī)器是讀不懂的,因此還必須想辦法將字節(jié)碼轉(zhuǎn)化成平臺(tái)相關(guān)的機(jī)器碼(也就是系統(tǒng)能識(shí)別的0和1)。這個(gè)過程可以由解釋器來執(zhí)行,也可以有即時(shí)編譯器(JIT Compiler)來完成
? 具體步驟如下圖
?
執(zhí)行引擎內(nèi)部包括如下
語法糖
1.概述
? 語法糖是一種用來方便程序員代碼開發(fā)的手段,簡化程序開發(fā),但是不會(huì)提供實(shí)質(zhì)性的功能改造,但可以提高開發(fā)效率或者語法的嚴(yán)謹(jǐn)性或者減少編碼出錯(cuò)的機(jī)會(huì)。
? 總而言之,語法糖可以看作是編譯器實(shí)現(xiàn)的一種小把戲。
2.泛型和類型擦除
? 泛型的本質(zhì)是參數(shù)化類型,也就是操作的數(shù)據(jù)類型本身也是一個(gè)參數(shù)。這種參數(shù)類型可以用在類,接口,方法中,分別叫泛型類,泛型接口,泛型方法。
? 但是java的泛型是一個(gè)語法糖,并非是真實(shí)泛型,只在源碼中存在,List和List 在編譯之后,就是List 并在相應(yīng)的地方加上了類型轉(zhuǎn)換代碼。這種實(shí)現(xiàn)方式叫類型擦除,也叫偽泛型。
但是,擦除法所謂的擦除,僅僅是對(duì)方法的code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過反射手段獲取參數(shù)化類型的根本依據(jù)。
泛型:
public class b {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("hello","a");
System.out.println(map.get("hello"));
}
}
實(shí)際上:
public class b {
public b(){
}
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("hello","a");
System.out.println(map.get("hello"));
}
}
3…自動(dòng)裝箱和遍歷循環(huán)
自動(dòng)裝箱和遍歷循環(huán)
public class b {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4);
for (Integer integer:
list) {
System.out.println(integer);
}
}
}
實(shí)際上
public class b {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(Integer.valueOf(1),Integer.valueOf(2),Integer.valueOf(3),Integer.valueOf(4));
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer next = (Integer) iterator.next();
System.out.println(next);
}
}
自動(dòng)裝箱用了Integer.valueOf
for循環(huán)用了迭代器
4.條件編譯
? —般情況下,程序中的每一行代碼都要參加編譯。但有時(shí)候出于對(duì)程序代碼優(yōu)化的考慮,希望只對(duì)其中一部分內(nèi)容進(jìn)行編譯,此時(shí)就需要在程序中加上條件,讓編譯器只對(duì)滿足條件的代碼進(jìn)行編譯,將不滿足條件的代碼舍棄,這就是條件編譯。
反編譯之前
public static void main(String[] args) {
if(true){
System.out.println("hello");
}else{
System.out.println("beybey");
}
}
反編譯之后
public static void main(String[] args) {
System.out.println("hello");
}
首先,我們發(fā)現(xiàn),在反編譯后的代碼中沒有System.out.println(“beybey”);,這其實(shí)就是條件編譯。
當(dāng)if(tue)為true的時(shí)候,編譯器就沒有對(duì)else的代碼進(jìn)行編譯。
所以,Java語法的條件編譯,是通過判斷條件為常量的if語句實(shí)現(xiàn)的。根據(jù)if判斷條件的真假,編譯器直接把分支為false的代碼塊消除。通過該方式實(shí)現(xiàn)的條件編譯,必須在方法體內(nèi)實(shí)現(xiàn),而無法在正整個(gè)Java類的結(jié)構(gòu)或者類的屬性上進(jìn)行條件編譯。
運(yùn)行期優(yōu)化
? Java 語言的 “編譯期” 其實(shí)是一段 “不確定” 的操作過程,因?yàn)樗赡苁侵敢粋€(gè)前端編譯器把 *.java 文件轉(zhuǎn)變成 *.class 文件的過程;也可能是指虛擬機(jī)的后端運(yùn)行期編譯器(JIT 編譯器,Just In Time Compiler)把字節(jié)碼轉(zhuǎn)變成機(jī)器碼的過程;還可能是指使用靜態(tài)提前編譯器(AOT 編譯器,Ahead Of Time Compiler)直接把 *.java 文件編譯成本地機(jī)器代碼的過程
1.解釋器與編譯器
什么是解釋器?
? 大概意思:
? 在計(jì)算機(jī)科學(xué)中,解釋器是一種計(jì)算機(jī)程序,它直接執(zhí)行由編程語言或腳本語言編寫的代碼,并不會(huì)把源代碼預(yù)編譯成機(jī)器碼。一個(gè)解釋器,通常會(huì)用以下的姿勢(shì)來執(zhí)行程序代碼:
? 分析源代碼,并且直接執(zhí)行。
? 把源代碼翻譯成相對(duì)更加高效率的中間碼,然后立即執(zhí)行它。
? 執(zhí)行由解釋器內(nèi)部的編譯器預(yù)編譯后保存的代碼
? 可以把解釋器看成一個(gè)黑盒子,我們輸入源碼,它就會(huì)實(shí)時(shí)返回結(jié)果。
? 不同類型的解釋器,黑盒子里面的構(gòu)造不一樣,有些還會(huì)集成編譯器,緩存編譯結(jié)果,用來提高執(zhí)行效率(例如 Chrome V8 也是這么做的)。
? 解釋器通常是工作在「運(yùn)行時(shí)」,并且對(duì)于我們輸入的源碼,是一行一行的解釋然后執(zhí)行,然后返回結(jié)果。
? 什么是編譯器?
? 源文件經(jīng)過編譯器編譯后才可生成二進(jìn)制文件,編譯過程包括預(yù)處理、編譯、匯編和鏈接,日常交流中常用“編譯”稱呼此四個(gè)過程
2.編譯對(duì)象與觸發(fā)條件
"熱點(diǎn)代碼"分兩類,
? 第一類是被多次調(diào)用的方法-這是由方法調(diào)用觸發(fā)的編譯。
? 第二類是被多次執(zhí)行的循環(huán)體 – 盡管編譯動(dòng)作是由循環(huán)體所觸發(fā)的,但編譯器依然會(huì)以整個(gè)方法(而不是單獨(dú)的循環(huán)體)作為編譯對(duì)象。
判斷一段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時(shí)編譯,這樣的行為稱為熱點(diǎn)探測(cè)(Hot Spot Detection);熱點(diǎn)探測(cè)判定方式有兩種:
? 第一種是基于采樣的熱點(diǎn)探測(cè)
? 第二種是基于計(jì)數(shù)器的熱點(diǎn)探測(cè)
HotSpot虛擬機(jī)中使用的是基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方法,因此它為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter)和回邊計(jì)數(shù)器(Back EdgeCounter)。確定虛擬機(jī)運(yùn)行參數(shù)的前提下,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值,當(dāng)計(jì)數(shù)器超過閾值溢出了,就會(huì)觸發(fā)JIT編譯。
3.編譯優(yōu)化技術(shù)
經(jīng)典優(yōu)化技術(shù)
語言無關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達(dá)式消除。
語言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一:數(shù)組范圍檢查消除。
最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián)。
最前沿的優(yōu)化技術(shù)之一:逃逸分析。
公共子表達(dá)式消除
int d= (c * b)*12+a+ (a + b * c)
//編譯器檢測(cè)到“c * b”與“b* c”是一樣的表達(dá)式,而且在計(jì)算期間b與c的值是不變的。
int d=E*12+a+(a+E);
數(shù)組邊界檢查消除
if (foo != null) {
return foo.value;
} else {
throw new NullPointerException();
}
# 虛擬機(jī)隱式優(yōu)化;
try {
return foo.value;
} catch (Segment_Fault e) {
uncommon_trap(e);
4 Java與C/C++的編譯器對(duì)比
第一,因?yàn)榧磿r(shí)編譯器運(yùn)行占用的是用戶程序的運(yùn)行時(shí)間,具有很大的時(shí)間壓力,它能提供的優(yōu)化手段也嚴(yán)重受制于編譯成本
第二,Java語言是動(dòng)態(tài)的類型安全語言,這就意味著需要由虛擬機(jī)來確保程序不會(huì)違反語言語義或訪問非結(jié)構(gòu)化內(nèi)存
第三,Java語言中雖然沒有virtual關(guān)鍵字,但是使用虛方法的頻率卻遠(yuǎn)遠(yuǎn)大于C/C++語言
Java內(nèi)存模型與線程
1. 硬件效率與一致性
除了增加高速緩存之外,為了使得處理器內(nèi)部的運(yùn)算單元盡可能被充分利用,處理器可能會(huì)對(duì)輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化,處理器會(huì)在計(jì)算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個(gè)語句計(jì)算的先后順序與代碼中的順序一致。
2. Java內(nèi)存模型
? 主內(nèi)存與工作內(nèi)存
? Java內(nèi)存模型的主要目標(biāo):定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。
? 為了獲取較好的執(zhí)行效能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內(nèi)存進(jìn)行交互,也沒有限制即時(shí)編譯器進(jìn)行調(diào)整代碼執(zhí)行順序這類優(yōu)化操作。
? 內(nèi)存間的交互操作
? lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)志為一條線程獨(dú)占的狀態(tài)。
? unlock(解鎖):作用于主內(nèi)存中的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖 定。
? read(讀取):作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用。
? load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
? use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎。
? assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接受到的值賦給工作內(nèi)存的變量。
? store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
? write(寫入):作用于主內(nèi)存中的變量,它把store操作從主內(nèi)存中得到的變量值放入主內(nèi)存的變量中。
? 原子性、可見性與有序性
? 原子性(Atomicity):由Java內(nèi)存模型來直接保證的原子性變量操作包括read、load、assign、use、和write。
? 可見性(Visibility):可見性是指當(dāng)一個(gè)線程修改了共享變量的值,其它線程能夠立即得知這個(gè)修改。J
? 有序性(Ordering):如果在本線程內(nèi)觀察,所有的操作都是有序的;
3.Java與線程
? 線程的實(shí)現(xiàn)
? 使用內(nèi)核線程實(shí)現(xiàn)
? 內(nèi)核線程(Kernel-Level Thread,KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核來完成線程切換,內(nèi)核通過操作系統(tǒng)調(diào)度器對(duì)線程進(jìn)行調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上。每個(gè)內(nèi)核線程可以視為內(nèi)核的一個(gè)分身,這樣操作系統(tǒng)就有能力同時(shí)處理多件事情,支持多線程的內(nèi)核就叫多線程內(nèi)核。
使用用戶線程實(shí)現(xiàn)
? 從廣義上來講,一個(gè)線程只要不是內(nèi)核線程,就可以認(rèn)為是用戶線程,因此,從這個(gè)定義上來講,輕量級(jí)進(jìn)程也屬于用戶線程,但輕量級(jí)進(jìn)程的實(shí)現(xiàn)始終是建立在內(nèi)核之上的,許多操作都進(jìn)行系統(tǒng)調(diào)用,效率會(huì)受到限制。
而狹義上的用戶線程指的是完全建立在用戶空間的線程庫上,系統(tǒng)內(nèi)核不能感知線程存在的實(shí)現(xiàn)。
? 使用用戶線程的優(yōu)勢(shì)在于不需要系統(tǒng)內(nèi)核的支援。劣勢(shì)也在于沒有系統(tǒng)內(nèi)核的支援,所有的線程操作都需要用戶程序自己處理。
使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)
? 在這種混合模式下,即存在用戶線程,也存在輕量級(jí)進(jìn)程。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建、切換、析構(gòu)等操作依然廉價(jià),并且可以支持大規(guī)模的用戶線程并發(fā)
4.Java線程的狀態(tài)轉(zhuǎn)化
新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)。
運(yùn)行(Runable):Runable包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是說處于此種狀態(tài)的線程可能正在執(zhí)行,也可能正在等待CPU為它分配執(zhí)行時(shí)間。
無限期等待(Waiting):處于這種狀態(tài)下的線程不會(huì)被分配CPU執(zhí)行時(shí)間,他們要等待被其他線程顯示喚醒。
限期等待(Timed Waiting):處于這種狀態(tài)下的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過無須等待被其他線程顯示喚醒,在一定時(shí)間之后它們由系統(tǒng)自動(dòng)喚醒。
阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取一個(gè)排他鎖,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生。
結(jié)束(Terminate):已經(jīng)終止的線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行。
線程安全與鎖優(yōu)化
1.線程安全
? 當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那這個(gè)對(duì)象是線程安全的
2.Java 語言中的線程安全
? 2.1不可變
? 在 Java 語言中,不可變線程一定是安全的,無論是對(duì)象的方法實(shí)現(xiàn)還是方法的調(diào)用者,都不需要采取任何的線程安全保障措施
? 其中最簡單的就是把對(duì)象中帶有狀態(tài)的變量都聲明為 final,這樣在構(gòu)造函數(shù)結(jié)束之后,它就是不可變的
? 2.2絕對(duì)線程安全
private static Vector<Integer> vector = new Vector<Integer>();
1
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread removeThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
});
Thread printThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
}
});
removeThread.start();
printThread.start();
// 不要同時(shí)產(chǎn)生過多的線程,否則會(huì)導(dǎo)致操作系統(tǒng)假死
while (Thread.activeCount() > 20);
}
}
3.相對(duì)線程安全
相對(duì)的線程安全就是我們通常意義上所講的線程安全,它需要保證對(duì)這個(gè)對(duì)象單獨(dú)的操作是線程安全,我們?cè)谡{(diào)用的時(shí)候不需要做額外的保障措施,但是對(duì)于一些特定順序的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來保證調(diào)用的正確性。
4.線程兼容
線程兼容是指對(duì)象本身并不是線程安全的,但是可以通過在調(diào)用端正確地使用同步手段來保證對(duì)象在并發(fā)環(huán)境中可以安全地使用,我們平常說一個(gè)類不是線程安全的,絕大多數(shù)時(shí)候指的是這一種情況。
5.線程對(duì)立
線程對(duì)立是指無論調(diào)用端是否采取了同步措施,都無法在多線程環(huán)境中并發(fā)使用的代碼。由于 Java 語言天生就具備多線程特性,線程對(duì)立這種排斥多線程的代碼是很少出現(xiàn)的,而且通常都是有害的,應(yīng)當(dāng)盡量避免。
線程安全的實(shí)現(xiàn)方法
1.互斥同步
同步是指在多個(gè)線程并發(fā)訪問共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)(或者是一些,使用信號(hào)量的時(shí)候)線程使用,而互斥是實(shí)現(xiàn)同步的一種手段,互斥是方法,同步是目的
2.非阻塞同步
測(cè)試并設(shè)置,獲取并增加,交換,比較并交換,加載連接 / 條件存儲(chǔ)
3.無同步方案
? 要保證線程安全,不一定非要保證線程同步,還可以有其他的方案
? 1.可重入代碼
? 2.線程本地存儲(chǔ)
鎖優(yōu)化
自旋鎖和自適應(yīng)自旋鎖
如果鎖在很短的時(shí)間內(nèi)釋放了,那么自旋的效果就很好
偏向鎖
? 偏向鎖的意思是這個(gè)鎖會(huì)偏向第一個(gè)獲取到他的鎖,如果在接下來執(zhí)行的過程中,該鎖一直沒有被其他的鎖獲取的話,則持有偏向鎖的線程永遠(yuǎn)不需要再進(jìn)行同步.一旦有新的線程試圖獲取該鎖,偏向模式會(huì)被撤銷.撤銷后進(jìn)入無鎖狀態(tài).這里會(huì)改變對(duì)象頭的關(guān)于偏性模式的標(biāo)志位和關(guān)于鎖的標(biāo)志位
輕量級(jí)鎖
當(dāng)使用輕量級(jí)鎖(鎖標(biāo)識(shí)位為00)時(shí),線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中
鎖粗化
? 這個(gè)原則大部分時(shí)間是對(duì)的但是如果一個(gè)系列的連續(xù)操作都是對(duì)同一個(gè)對(duì)象反復(fù)的加鎖和解鎖,甚至加鎖操作出現(xiàn)在循環(huán)體之中,即使沒有線程競(jìng)爭(zhēng),頻繁的進(jìn)行互斥同步的操作也會(huì)導(dǎo)致不必要的性能損耗.
鎖消除
public String concatString(String s1, String s2){
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
? 我們發(fā)現(xiàn)sb的引用被限制在了concatStirng方法里面他永遠(yuǎn)不可能被其他線程訪問到,因此雖然這里有鎖但是可以被安全的消除掉.在解釋執(zhí)行時(shí)這里仍然會(huì)枷鎖,但是經(jīng)過服務(wù)端編譯器即時(shí)編譯后,這段代碼會(huì)自動(dòng)忽略所有的同步措施直接執(zhí)行.
最后
大家看完有什么不懂的可以在下方留言討論,也可以關(guān)注我私信問我,我看到后都會(huì)回答的。謝謝你的觀看,覺得文章對(duì)你有幫助的話記得關(guān)注我點(diǎn)個(gè)贊支持一下!