前言
前兩天和朋友探討技術(shù)的時候有聊到JVM和JDK這一塊左冬,聊到這里兩個人就像高山流水遇知音那是根本停不下來桐筏,事后我想著趁現(xiàn)在印象還比較深刻就把這些東西整理起來分享給大家來幫助更多的人吧。話不多說拇砰,滿滿的干貨都整理在下面了梅忌!
JVM探究
jvm的位置
jvm的體系結(jié)構(gòu)
堆里面有垃圾,需要被GC回收
棧里面是沒有垃圾的除破,用完就彈出去了牧氮,棧里面有垃圾,程序就崩了瑰枫,執(zhí)行不完main方法踱葛。
Java棧,本地方法棧光坝,程序計數(shù)器里面是不可能存在垃圾的尸诽。也就不會有垃圾回收。
所謂的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();
類是一個模板,是抽象的土铺,而new出來的對象是具體的,是對這個抽象的類的實例化
1.虛擬機(jī)自帶的加載器
2.啟動類(根)加載器
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.類加載器收到類加載的請求
2.將這個請求向上委托給父類加載器去完成板鬓,一直向上委托悲敷,直到根加載器
3.啟動類加載器會檢查是否能夠加載當(dāng)前這個類,能加載就結(jié)束俭令,使用當(dāng)前加載器后德,否則,拋出異常抄腔,通知子類加載器進(jìn)行加載瓢湃。
4.重復(fù)步驟3
若都找不到就會報 Class Not Found
null:Java調(diào)用不到理张,可能編程語言是C寫的,所以調(diào)不到
Java =C+±- 去掉C里面比較繁瑣的東西 指針绵患,內(nèi)存管理(JVM幫你做了)
雙親委派機(jī)制
雙親委派機(jī)制:安全
APP–>EXC–BOOTStrap(根目錄雾叭,最終執(zhí)行)
當(dāng)某個類加載器需要加載某個.class文件時,它首先把這個任務(wù)委托給他的上級類加載器落蝙,遞歸這個操作织狐,如果上級的類加載器沒有加載,自己才會去加載這個類筏勒。
在src下創(chuàng)建Java.lang包移迫,創(chuàng)建一個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é)果
它會去最終的BOOTStrap里面的String類里面去執(zhí)行,找到執(zhí)行類的位置管行,發(fā)現(xiàn)里面沒有要執(zhí)行的mian方法厨埋,所以會報這個錯。
在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ù)加載同一個.class。通過委托去向上面問一問套菜,加載過了亲善,就不用再加載一遍。保證數(shù)據(jù)安全逗柴。
2蛹头、保證核心.class不能被篡改。通過委托方式戏溺,不會去篡改核心.class渣蜗,即使篡改也不會去加載,即使加載也不會是同一個.class對象了旷祸。不同的加載器加載同一個.class也不是同一個Class對象耕拷。這樣保證了Class執(zhí)行安全。
沙箱安全機(jī)制
? Java安全模型的核心就是Java沙箱(sandbox)托享,什么是沙箱骚烧?沙箱是一個限制程序運(yùn)行的環(huán)境,沙箱機(jī)制就是將Java代碼限定在虛擬機(jī)(JVM)特定的運(yùn)行范圍中闰围,并且嚴(yán)格限制代碼對本地系統(tǒng)資源訪問赃绊,通過這樣的措施來保證對代碼的有效隔離,防止對本地系統(tǒng)造成破壞羡榴,沙箱主要限制系統(tǒng)資源訪問碧查,那系統(tǒng)資源包括什么?CPU,內(nèi)存忠售,文件系統(tǒng)传惠,網(wǎng)格,不同級別的沙箱對這些資源訪問的限制也是可以不一樣稻扬。
? 所以的Java程序運(yùn)行都可以指定沙箱卦方,可以定制安全策略。
? 在Java中將執(zhí)行程序分為本地代碼呵遠(yuǎn)程代碼兩種腐螟,本地代碼默認(rèn)視為可信任的愿汰,而遠(yuǎn)程代碼則被看作是不受信任的,對于授權(quán)的本地代碼乐纸,可以訪問一切本地資源衬廷,而對于非授信的遠(yuǎn)程代碼在早期的Java實現(xiàn)中,安全依賴于沙箱機(jī)制汽绢,如下圖jdk1.0安全模型
但如此嚴(yán)格的安全機(jī)制也給程序的功能擴(kuò)展帶來障礙吗跋,比如當(dāng)用戶希望遠(yuǎn)程代碼訪問本地系統(tǒng)的文件時候,就無法實現(xiàn)宁昭,因此在后續(xù)的Java1.1版本中跌宛,針對安全機(jī)制做了改進(jìn),增加了安全策略积仗,允許用戶指定代碼本地資源的訪問權(quán)限疆拘,如下圖所示JDK1.1安全模型
? 在Java1.2版本中,再次改進(jìn)了安全機(jī)制寂曹,增加了代碼簽名哎迄,不論本地代碼或是遠(yuǎn)程代碼,都會按照用戶的安全策略設(shè)定隆圆,由類加載器加載到虛擬機(jī)中權(quán)限不同的運(yùn)行空間漱挚,來實現(xiàn)差異化的代碼執(zhí)行權(quán)限控制,如下圖所JDK1.2安全模型
當(dāng)前最新的安全機(jī)制實現(xiàn)渺氧,則引入域(Domain)的概念旨涝,虛擬機(jī)會把所有代碼加載到不同的系統(tǒng)域和應(yīng)用域,系統(tǒng)域部分專門負(fù)責(zé)與關(guān)鍵資源進(jìn)行交互侣背,而各個應(yīng)用域部分則通過系統(tǒng)域的部分代理來各種需求的資源進(jìn)行訪問白华,虛擬機(jī)中不同的受保護(hù)域,對應(yīng)不一樣的權(quán)限贩耐,存在不同域中的類文件就具有了當(dāng)前域的全部權(quán)限衬鱼,如下圖所示,最新的安全模型
組成沙箱的基本組件:
字節(jié)碼校驗器(bytecode verifier):確保Java類文件遵循Java語言規(guī)范憔杨,這樣可以幫助Java程序?qū)崿F(xiàn)內(nèi)存保護(hù),但并不是所有的類文件都會經(jīng)過字節(jié)碼校驗蒜胖,比如核心類消别。
類加載器(class loader):其中類加載器在3個方面對Java沙箱起作用
? 它防止惡意代碼去干涉善意的代碼抛蚤;//雙親委派機(jī)制
? 它守護(hù)了被信任的類庫邊界;
? 它將代碼歸入保護(hù)域寻狂,確定了代碼可以進(jìn)行哪些操作岁经;
? 虛擬機(jī)為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成蛇券,每一個被裝載的類將有一個名字缀壤,這個命名空間由Java虛擬機(jī)為每一個類加載器維護(hù)的,它們互相甚至不可見纠亚。
? 類加載器采用的機(jī)制是雙親委派模式塘慕。
? 虛擬機(jī)為不同的類加載開始加載,外層惡意同名類得不到加載從而無法使用蒂胞;
? 由于嚴(yán)格通過包來區(qū)分了訪問域:外層惡意的類通過內(nèi)置代碼也無法獲得權(quán)限訪問到內(nèi)置類图呢,破壞代碼就自然無法生效。
? 存取控制器(access controller):存取控制器可以控制核心API對操作系統(tǒng)的存取權(quán)限骗随,而這個控制的策略設(shè)定蛤织,可以由用戶指定。
? 安全管理器 (security manager):是核心API和操作系統(tǒng)之間的主要接口鸿染,實現(xiàn)權(quán)限控制指蚜,比存取控制器優(yōu)先級高。
? 安全軟件包(security package):java.security下的類和擴(kuò)展包下的類涨椒,允許用戶為自己的應(yīng)用增加新的安全特性摊鸡,包括:
? 安全提供者
? 信息摘要
? 數(shù)字簽名 kettools https(需要證書)
? 加密
? 鑒別
Native
凡是帶了native關(guān)鍵字,說明Java的的作用范圍達(dá)不到了丢烘,會去調(diào)用底層C語言的庫柱宦!
會進(jìn)入本地方法棧
調(diào)用本地方法本地接口 JNI
JNI的作用:擴(kuò)展Java的使用,融合不同的編程語言為Java所用播瞳,最初:C掸刊,C++
Java誕生的時候C ,C++比較火,Java想要立足赢乓,必須要有調(diào)用C忧侧,C++的程序。
他在內(nèi)存區(qū)域?qū)iT開辟了一塊標(biāo)記區(qū)域 :Native Method Stack牌芋,登記native方法
在最終執(zhí)行的時候蚓炬,加載本地方法庫中的方法通過JNI
Java程序驅(qū)動打印機(jī),管理系統(tǒng)躺屁,掌握即可肯夏,在企業(yè)級應(yīng)用中較為少見。
? 目前該方法使用使用的越來越少了,除非 是與硬件有關(guān)的應(yīng)用驯击,比如通過Java程序驅(qū)動打印機(jī)或者Java系統(tǒng)管理生產(chǎn)設(shè)備烁兰,在企業(yè)級應(yīng)用中已經(jīng)比較少見,因為現(xiàn)在的異構(gòu)領(lǐng)域間通信很發(fā)達(dá)徊都,比如可以使用Socjet通信沪斟,也可以使Web Service等等 ,不多做介紹暇矫!
PC寄存器
? 程序計數(shù)器:Program Counter Register
? 每個線程都有一個程序計數(shù)器主之,要線程私有的,就是一個指針李根,指向方法區(qū)中的方法字節(jié)碼(用來存儲指向像一條指令的地址槽奕,也即將要執(zhí)行的指令代碼),在執(zhí)行引擎讀取下一條指令朱巨,是一個非常小的內(nèi)存空間史翘,幾乎可以忽略不計。
方法區(qū)
Method Area 方法區(qū)
? 方法區(qū)是被所有線程共享冀续,所有字段和方法字節(jié)碼琼讽,以及一些特殊方法,如構(gòu)造函數(shù)洪唐,接口代碼也在此定義钻蹬,簡單說,所有定義的方法的信息都保存在該區(qū)域凭需,在此區(qū)域?qū)儆诠蚕韰^(qū)間
? 靜態(tài)變量问欠,常量,類信息(構(gòu)造方法粒蜈,接口定義)顺献,運(yùn)行時的常量池存在方法區(qū)中,但是實例變量存在堆內(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)先出
隊列:先進(jìn)先出(FIFO:First input First Output)
方法運(yùn)行完成以后蕊程,就會被棧彈出去
兩個方法互相調(diào)椒袍,就會導(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)存也就釋放了,對于棧來說兼吓,不存在垃圾回收問題,一旦線程結(jié)束森枪,棧就沒了视搏。
棧:8大基本類型+對象引用+實例的方法
棧運(yùn)行原理:棧幀
函數(shù)調(diào)用過程中,肯定需要空間的開辟县袱,而調(diào)用這個函數(shù)時為該函數(shù)開辟的空間就叫做該函數(shù)的棧幀
程序正在執(zhí)行的方法一定在棧的頂部浑娜,執(zhí)行完就會彈出去
棧1在運(yùn)行完成之后就會彈出去,然后棧2在去執(zhí)行式散,棧2執(zhí)行完筋遭,程序也就結(jié)束了
棧滿了:StackOverflowError
棧+堆+方法區(qū) :交互
如下圖:對象在內(nèi)存中實例化的過程
三種jvm
Sun 公司 HotSpot Java HotSpot? 64-Bit Server VM (build 25.77-b03, mixed mode)
BEA :JRockit
IBM :J9 VM
我們用的是hotspot
堆
Heap(堆):一個jvm只有一個,堆內(nèi)存的大小是可以調(diào)節(jié)的暴拄。
類加載器讀取了類文件后漓滔,一般會把什么東西放到堆中呢?類乖篷,方法响驴,常量,保存我們所有引用類型的真實對象撕蔼。
堆內(nèi)存中還要細(xì)分為三個區(qū)域:
新生區(qū)(伊甸園區(qū))
養(yǎng)老區(qū)
永久區(qū)
GC垃圾回收豁鲤,主要是在伊甸園區(qū)和養(yǎng)老區(qū),
假設(shè)內(nèi)存滿了鲸沮,會報OOM琳骡,堆內(nèi)存不夠,堆溢出
在jdk8以后讼溺,永久存儲區(qū)改了個名字叫(元空間)
新生區(qū)
它是一個類:誕生和成長的地方楣号,甚至死亡;
新生區(qū)分為 伊甸園區(qū)和幸存者區(qū)
伊甸園區(qū);所有的對象都是在伊甸園區(qū)里new出來的
幸存區(qū):(0肾胯,1)
當(dāng)伊甸園區(qū)滿了以后竖席,會觸發(fā)輕GC,對伊甸園區(qū)進(jìn)行垃圾回收敬肚,當(dāng)某個對象通過GC幸存下來以后毕荐,就會進(jìn)入到幸存者區(qū)员寇,依次不斷的循環(huán)第美,當(dāng)幸存0區(qū)和1區(qū)也滿了的時候蝶锋,在經(jīng)歷過多次GC以后,活下來的對象什往,也就是被重新引用的對象就會進(jìn)入到老年區(qū)扳缕。而當(dāng)老年區(qū)也滿了的時候躯舔,就會觸發(fā)重GC,重CG除了會去清理老年區(qū)的省古,還會伊甸園區(qū)和幸存0區(qū)1區(qū)的所有垃圾全清理掉粥庄。而在重GC清除下,活下來的就會進(jìn)入到養(yǎng)老區(qū)琳拭。當(dāng)重GC清理完畢以后训堆,新生區(qū)和養(yǎng)老區(qū)還是都滿了,這個時候就會報堆溢出的報錯臀栈。
真理:經(jīng)過研究蔫慧,99%對象都是臨時對象。
老年區(qū)
新生區(qū)里面GC不掉的對象就會去到老年區(qū)
永久區(qū)
這個區(qū)域常駐內(nèi)存的权薯,用來存放JDK自身攜帶的Class對象姑躲,Interface元數(shù)據(jù),存儲的是Java運(yùn)行時的一些環(huán)境或類信息盟蚣,這個區(qū)域不存在垃圾回收黍析!關(guān)閉VM虛擬機(jī)就會釋放這個區(qū)域的內(nèi)存。
一個啟動類加載了大量的第三方j(luò)ar包屎开,Tomcat部署了太多的應(yīng)用阐枣,大量動態(tài)生成的反射類,不斷的被加載奄抽,直到內(nèi)存滿蔼两,就會出現(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ù)包堆溢出的錯阿逃,我們就該去考慮考慮自己代碼那塊有問題铭拧,可能是有垃圾代碼或者是死循環(huán)代碼,它在不斷的占用內(nèi)存空間
? 分析內(nèi)存恃锉,看一下那個地方出現(xiàn)了問題(專業(yè)工具)
? 調(diào)優(yōu)代碼-Xms1024m -Xmx1024m -XX:+PrintGCDetails
Xms后面填計算機(jī)給jvm分配的內(nèi)存羽历,Xmx后面填Jvm初始的內(nèi)存值
在一個項目中,突然出現(xiàn)了OOM故障淡喜,那該如何排除研究為什么出錯~
能夠看到代碼第幾行出錯;
Dubug:一行行分析代碼诵闭!
MAT,Jprofiler作用:
分析Dump內(nèi)存文件炼团,快速定位內(nèi)存泄漏;
獲得堆中的數(shù)據(jù)
獲得大的對象~
疏尿。瘟芝。。褥琐。
GC
jvm在進(jìn)行垃圾回收時锌俱,并不是對這三個區(qū)域統(tǒng)一回收,大部分時候敌呈,回收但是新生代
新生代
幸存區(qū)(from贸宏,to)
老年區(qū)
GC兩種類:輕GC(普通的GC),重GC(全局GC)
GC常用算法
標(biāo)記清除法
? 最基礎(chǔ)的收集算法是“標(biāo)記-清除”(Mark-Sweep)算法磕洪,如它的名字一樣吭练,算法分為“標(biāo)記”和“清除”兩個階段:
? 首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對象析显,之所以說它是最基礎(chǔ)的收集算法鲫咽,是因為后續(xù)的收集算法都是基于這種思路并對其缺點(diǎn)進(jìn)行改進(jìn)而得到的。
? 它的主要缺點(diǎn)有兩個:一個是效率問題谷异,標(biāo)記和清除過程的效率都不高分尸;另外一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片歹嘹,空間碎片太多可能會導(dǎo)致箩绍,當(dāng)程序在以后的運(yùn)行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。標(biāo)記-清除算法的執(zhí)行過程(需要較大內(nèi)存時卻不夠了就要回收一次)
復(fù)制算法
為了解決效率問題荞下,一種稱為“復(fù)制”(Copying)的收集算法出現(xiàn)了伶选,它將可用內(nèi)存按容量劃分為大小相等的兩塊史飞,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了仰税,就將還存活著的對象復(fù)制到另外一塊上面构资,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對其中的一塊進(jìn)行內(nèi)存回收陨簇,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況吐绵,只要移動堆頂指針,按順序分配內(nèi)存即可河绽,實現(xiàn)簡單己单,運(yùn)行高效。只是這種算法的代價是將內(nèi)存縮小為原來的一半耙饰,未免太高了一點(diǎn)纹笼。
標(biāo)記-整理算法
復(fù)制收集算法在對象存活率較高時就要執(zhí)行較多的復(fù)制操作,效率將會變低苟跪。更關(guān)鍵的是廷痘,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保件已,以應(yīng)對被使用的內(nèi)存中所有對象都100%存活的極端情況笋额,所以在老年代一般不能直接選用這種算法。
分代收集算法(并不是一種新的思想篷扩,只是將java堆分成新生代和老年代兄猩,根據(jù)各自特點(diǎn)采用不同算法)
當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什么新的思想鉴未,只是根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊枢冤。一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ㄍ选T谛律刑偷迹看卫占瘯r都發(fā)現(xiàn)有大批對象死去,只有少量存活羽峰,那就選用復(fù)制算法趟咆,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因為對象存活率高梅屉、沒有額外空間對它進(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)化成平臺相關(guān)的機(jī)器碼(也就是系統(tǒng)能識別的0和1)荒吏。這個過程可以由解釋器來執(zhí)行夫否,也可以有即時編譯器(JIT Compiler)來完成
? 具體步驟如下圖
?
執(zhí)行引擎內(nèi)部包括如下
語法糖
1.概述
? 語法糖是一種用來方便程序員代碼開發(fā)的手段,簡化程序開發(fā)柒啤,但是不會提供實質(zhì)性的功能改造家肯,但可以提高開發(fā)效率或者語法的嚴(yán)謹(jǐn)性或者減少編碼出錯的機(jī)會龄砰。
? 總而言之,語法糖可以看作是編譯器實現(xiàn)的一種小把戲讨衣。
2.泛型和類型擦除
? 泛型的本質(zhì)是參數(shù)化類型寝贡,也就是操作的數(shù)據(jù)類型本身也是一個參數(shù)。這種參數(shù)類型可以用在類值依,接口,方法中碟案,分別叫泛型類愿险,泛型接口,泛型方法价说。
? 但是java的泛型是一個語法糖辆亏,并非是真實泛型,只在源碼中存在鳖目,List和List 在編譯之后扮叨,就是List 并在相應(yīng)的地方加上了類型轉(zhuǎn)換代碼。這種實現(xiàn)方式叫類型擦除领迈,也叫偽泛型彻磁。
但是,擦除法所謂的擦除狸捅,僅僅是對方法的code屬性中的字節(jié)碼進(jìn)行擦除衷蜓,實際上元數(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"));
}
}
實際上:
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…自動裝箱和遍歷循環(huán)
自動裝箱和遍歷循環(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);
}
}
}
實際上
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);
}
}
自動裝箱用了Integer.valueOf
for循環(huán)用了迭代器
4.條件編譯
? —般情況下磁浇,程序中的每一行代碼都要參加編譯。但有時候出于對程序代碼優(yōu)化的考慮朽褪,希望只對其中一部分內(nèi)容進(jìn)行編譯置吓,此時就需要在程序中加上條件无虚,讓編譯器只對滿足條件的代碼進(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”);咆爽,這其實就是條件編譯。
當(dāng)if(tue)為true的時候置森,編譯器就沒有對else的代碼進(jìn)行編譯斗埂。
所以,Java語法的條件編譯凫海,是通過判斷條件為常量的if語句實現(xiàn)的呛凶。根據(jù)if判斷條件的真假,編譯器直接把分支為false的代碼塊消除行贪。通過該方式實現(xiàn)的條件編譯漾稀,必須在方法體內(nèi)實現(xiàn),而無法在正整個Java類的結(jié)構(gòu)或者類的屬性上進(jìn)行條件編譯建瘫。
運(yùn)行期優(yōu)化
? Java 語言的 “編譯期” 其實是一段 “不確定” 的操作過程崭捍,因為它可能是指一個前端編譯器把 *.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ī)科學(xué)中橄浓,解釋器是一種計算機(jī)程序粒梦,它直接執(zhí)行由編程語言或腳本語言編寫的代碼,并不會把源代碼預(yù)編譯成機(jī)器碼荸实。一個解釋器匀们,通常會用以下的姿勢來執(zhí)行程序代碼:
? 分析源代碼,并且直接執(zhí)行准给。
? 把源代碼翻譯成相對更加高效率的中間碼泄朴,然后立即執(zhí)行它。
? 執(zhí)行由解釋器內(nèi)部的編譯器預(yù)編譯后保存的代碼
? 可以把解釋器看成一個黑盒子露氮,我們輸入源碼叼旋,它就會實時返回結(jié)果。
? 不同類型的解釋器沦辙,黑盒子里面的構(gòu)造不一樣夫植,有些還會集成編譯器,緩存編譯結(jié)果,用來提高執(zhí)行效率(例如 Chrome V8 也是這么做的)详民。
? 解釋器通常是工作在「運(yùn)行時」延欠,并且對于我們輸入的源碼,是一行一行的解釋然后執(zhí)行沈跨,然后返回結(jié)果由捎。
? 什么是編譯器?
? 源文件經(jīng)過編譯器編譯后才可生成二進(jìn)制文件饿凛,編譯過程包括預(yù)處理狞玛、編譯、匯編和鏈接涧窒,日常交流中常用“編譯”稱呼此四個過程
2.編譯對象與觸發(fā)條件
"熱點(diǎn)代碼"分兩類心肪,
? 第一類是被多次調(diào)用的方法-這是由方法調(diào)用觸發(fā)的編譯。
? 第二類是被多次執(zhí)行的循環(huán)體 – 盡管編譯動作是由循環(huán)體所觸發(fā)的纠吴,但編譯器依然會以整個方法(而不是單獨(dú)的循環(huán)體)作為編譯對象硬鞍。
判斷一段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時編譯戴已,這樣的行為稱為熱點(diǎn)探測(Hot Spot Detection)固该;熱點(diǎn)探測判定方式有兩種:
? 第一種是基于采樣的熱點(diǎn)探測
? 第二種是基于計數(shù)器的熱點(diǎn)探測
HotSpot虛擬機(jī)中使用的是基于計數(shù)器的熱點(diǎn)探測方法,因此它為每個方法準(zhǔn)備了兩類計數(shù)器:方法調(diào)用計數(shù)器(Invocation Counter)和回邊計數(shù)器(Back EdgeCounter)糖儡。確定虛擬機(jī)運(yùn)行參數(shù)的前提下伐坏,這兩個計數(shù)器都有一個確定的閾值,當(dāng)計數(shù)器超過閾值溢出了握联,就會觸發(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 * b”與“b* c”是一樣的表達(dá)式呐矾,而且在計算期間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++的編譯器對比
第一蜒犯,因為即時編譯器運(yùn)行占用的是用戶程序的運(yùn)行時間,具有很大的時間壓力荞膘,它能提供的優(yōu)化手段也嚴(yán)重受制于編譯成本
第二罚随,Java語言是動態(tài)的類型安全語言,這就意味著需要由虛擬機(jī)來確保程序不會違反語言語義或訪問非結(jié)構(gòu)化內(nèi)存
第三羽资,Java語言中雖然沒有virtual關(guān)鍵字淘菩,但是使用虛方法的頻率卻遠(yuǎn)遠(yuǎn)大于C/C++語言
Java內(nèi)存模型與線程
1. 硬件效率與一致性
除了增加高速緩存之外,為了使得處理器內(nèi)部的運(yùn)算單元盡可能被充分利用,處理器可能會對輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化潮改,處理器會在計算之后將亂序執(zhí)行的結(jié)果重組狭郑,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中各個語句計算的先后順序與代碼中的順序一致汇在。
2. Java內(nèi)存模型
? 主內(nèi)存與工作內(nèi)存
? Java內(nèi)存模型的主要目標(biāo):定義程序中各個變量的訪問規(guī)則翰萨,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。
? 為了獲取較好的執(zhí)行效能糕殉,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內(nèi)存進(jìn)行交互亩鬼,也沒有限制即時編譯器進(jìn)行調(diào)整代碼執(zhí)行順序這類優(yōu)化操作。
? 內(nèi)存間的交互操作
? lock(鎖定):作用于主內(nèi)存的變量阿蝶,它把一個變量標(biāo)志為一條線程獨(dú)占的狀態(tài)雳锋。
? unlock(解鎖):作用于主內(nèi)存中的變量,它把一個處于鎖定狀態(tài)的變量釋放出來赡磅,釋放后的變量才可以被其他線程鎖 定魄缚。
? read(讀取):作用于主內(nèi)存的變量,它把一個變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中焚廊,以便隨后的load動作使用冶匹。
? load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中咆瘟。
? use(使用):作用于工作內(nèi)存的變量嚼隘,它把工作內(nèi)存中一個變量的值傳遞給執(zhí)行引擎。
? assign(賦值):作用于工作內(nèi)存的變量袒餐,它把一個從執(zhí)行引擎接受到的值賦給工作內(nèi)存的變量飞蛹。
? store(存儲):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個變量的值傳送到主內(nèi)存中灸眼,以便隨后的write操作使用卧檐。
? write(寫入):作用于主內(nèi)存中的變量,它把store操作從主內(nèi)存中得到的變量值放入主內(nèi)存的變量中焰宣。
? 原子性霉囚、可見性與有序性
? 原子性(Atomicity):由Java內(nèi)存模型來直接保證的原子性變量操作包括read、load匕积、assign盈罐、use、和write闪唆。
? 可見性(Visibility):可見性是指當(dāng)一個線程修改了共享變量的值盅粪,其它線程能夠立即得知這個修改。J
? 有序性(Ordering):如果在本線程內(nèi)觀察悄蕾,所有的操作都是有序的票顾;
3.Java與線程
? 線程的實現(xiàn)
? 使用內(nèi)核線程實現(xiàn)
? 內(nèi)核線程(Kernel-Level Thread,KLT)就是直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核來完成線程切換库物,內(nèi)核通過操作系統(tǒng)調(diào)度器對線程進(jìn)行調(diào)度霸旗,并負(fù)責(zé)將線程的任務(wù)映射到各個處理器上。每個內(nèi)核線程可以視為內(nèi)核的一個分身戚揭,這樣操作系統(tǒng)就有能力同時處理多件事情诱告,支持多線程的內(nèi)核就叫多線程內(nèi)核。
使用用戶線程實現(xiàn)
? 從廣義上來講民晒,一個線程只要不是內(nèi)核線程精居,就可以認(rèn)為是用戶線程,因此潜必,從這個定義上來講靴姿,輕量級進(jìn)程也屬于用戶線程,但輕量級進(jìn)程的實現(xiàn)始終是建立在內(nèi)核之上的磁滚,許多操作都進(jìn)行系統(tǒng)調(diào)用佛吓,效率會受到限制。
而狹義上的用戶線程指的是完全建立在用戶空間的線程庫上垂攘,系統(tǒng)內(nèi)核不能感知線程存在的實現(xiàn)维雇。
? 使用用戶線程的優(yōu)勢在于不需要系統(tǒng)內(nèi)核的支援。劣勢也在于沒有系統(tǒng)內(nèi)核的支援晒他,所有的線程操作都需要用戶程序自己處理吱型。
使用用戶線程加輕量級進(jìn)程混合實現(xiàn)
? 在這種混合模式下,即存在用戶線程陨仅,也存在輕量級進(jìn)程津滞。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建灼伤、切換触徐、析構(gòu)等操作依然廉價,并且可以支持大規(guī)模的用戶線程并發(fā)
4.Java線程的狀態(tài)轉(zhuǎn)化
新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)狐赡。
運(yùn)行(Runable):Runable包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready撞鹉,也就是說處于此種狀態(tài)的線程可能正在執(zhí)行,也可能正在等待CPU為它分配執(zhí)行時間猾警。
無限期等待(Waiting):處于這種狀態(tài)下的線程不會被分配CPU執(zhí)行時間孔祸,他們要等待被其他線程顯示喚醒隆敢。
限期等待(Timed Waiting):處于這種狀態(tài)下的線程也不會被分配CPU執(zhí)行時間发皿,不過無須等待被其他線程顯示喚醒,在一定時間之后它們由系統(tǒng)自動喚醒拂蝎。
阻塞(Blocked):線程被阻塞了穴墅,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發(fā)生。
結(jié)束(Terminate):已經(jīng)終止的線程的線程狀態(tài)玄货,線程已經(jīng)結(jié)束執(zhí)行皇钞。
線程安全與鎖優(yōu)化
1.線程安全
? 當(dāng)多個線程訪問一個對象時,如果不用考慮這些線程在運(yùn)行時環(huán)境下的調(diào)度和交替執(zhí)行松捉,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個對象的行為都可以獲得正確的結(jié)果复斥,那這個對象是線程安全的
2.Java 語言中的線程安全
? 2.1不可變
? 在 Java 語言中,不可變線程一定是安全的目锭,無論是對象的方法實現(xiàn)還是方法的調(diào)用者,都不需要采取任何的線程安全保障措施
? 其中最簡單的就是把對象中帶有狀態(tài)的變量都聲明為 final,這樣在構(gòu)造函數(shù)結(jié)束之后,它就是不可變的
? 2.2絕對線程安全
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();
// 不要同時產(chǎn)生過多的線程荣恐,否則會導(dǎo)致操作系統(tǒng)假死
while (Thread.activeCount() > 20);
}
}
3.相對線程安全
相對的線程安全就是我們通常意義上所講的線程安全臼膏,它需要保證對這個對象單獨(dú)的操作是線程安全检访,我們在調(diào)用的時候不需要做額外的保障措施卖氨,但是對于一些特定順序的連續(xù)調(diào)用密似,就可能需要在調(diào)用端使用額外的同步手段來保證調(diào)用的正確性抛猫。
4.線程兼容
線程兼容是指對象本身并不是線程安全的,但是可以通過在調(diào)用端正確地使用同步手段來保證對象在并發(fā)環(huán)境中可以安全地使用,我們平常說一個類不是線程安全的,絕大多數(shù)時候指的是這一種情況。
5.線程對立
線程對立是指無論調(diào)用端是否采取了同步措施炼七,都無法在多線程環(huán)境中并發(fā)使用的代碼陕悬。由于 Java 語言天生就具備多線程特性,線程對立這種排斥多線程的代碼是很少出現(xiàn)的,而且通常都是有害的裂问,應(yīng)當(dāng)盡量避免。
線程安全的實現(xiàn)方法
1.互斥同步
同步是指在多個線程并發(fā)訪問共享數(shù)據(jù)時,保證共享數(shù)據(jù)在同一個時刻只被一個(或者是一些虑瀑,使用信號量的時候)線程使用舌狗,而互斥是實現(xiàn)同步的一種手段,互斥是方法扔水,同步是目的
2.非阻塞同步
測試并設(shè)置魔市,獲取并增加,交換岂膳,比較并交換簸喂,加載連接 / 條件存儲
3.無同步方案
? 要保證線程安全除呵,不一定非要保證線程同步颜曾,還可以有其他的方案
? 1.可重入代碼
? 2.線程本地存儲
鎖優(yōu)化
自旋鎖和自適應(yīng)自旋鎖
如果鎖在很短的時間內(nèi)釋放了,那么自旋的效果就很好
偏向鎖
? 偏向鎖的意思是這個鎖會偏向第一個獲取到他的鎖,如果在接下來執(zhí)行的過程中,該鎖一直沒有被其他的鎖獲取的話,則持有偏向鎖的線程永遠(yuǎn)不需要再進(jìn)行同步.一旦有新的線程試圖獲取該鎖,偏向模式會被撤銷.撤銷后進(jìn)入無鎖狀態(tài).這里會改變對象頭的關(guān)于偏性模式的標(biāo)志位和關(guān)于鎖的標(biāo)志位
輕量級鎖
當(dāng)使用輕量級鎖(鎖標(biāo)識位為00)時纠拔,線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間诡曙,并將對象頭中的Mark Word復(fù)制到鎖記錄中
鎖粗化
? 這個原則大部分時間是對的但是如果一個系列的連續(xù)操作都是對同一個對象反復(fù)的加鎖和解鎖,甚至加鎖操作出現(xiàn)在循環(huán)體之中,即使沒有線程競爭,頻繁的進(jìn)行互斥同步的操作也會導(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í)行時這里仍然會枷鎖,但是經(jīng)過服務(wù)端編譯器即時編譯后,這段代碼會自動忽略所有的同步措施直接執(zhí)行.
最后
大家看完有什么不懂的可以在下方留言討論臀叙,也可以關(guān)注我私信問我,我看到后都會回答的价卤。謝謝你的觀看劝萤,覺得文章對你有幫助的話記得關(guān)注我點(diǎn)個贊支持一下!