棧和堆的區(qū)別
棧是運(yùn)行時單位岳守,代表著邏輯察藐,內(nèi)含基本數(shù)據(jù)類型和堆中對象引用,所在區(qū)域連續(xù)快骗,沒有碎片;
堆是存儲單位楚昭,代表著數(shù)據(jù),可以多個棧共享(包括成員中基本數(shù)據(jù)類型,引用和引用對象)太雨,所在區(qū)域不連續(xù),會有碎片魁蒜。
區(qū)別:
- 功能不同
棧內(nèi)存用來存儲局部變量的基本數(shù)據(jù)類型和對象的引用囊扳,而堆內(nèi)存用來存儲Java中的對象,無論是成員變量兜看,局部變量還是類變量锥咸,它們指向的對象都存儲在堆內(nèi)存中。
- 共享性不同
棧內(nèi)存是線程私有的
堆內(nèi)存是所有線程共享的
- 異常錯誤不同
如果棧內(nèi)存或者堆內(nèi)存不足都會拋出異常
椣敢疲空間不足:java.lang.StackOverFlowError.
堆空間不足:Java,lang.OutOfMemoryError.
- 空間大小
棧的空間大小遠(yuǎn)遠(yuǎn)小于堆的搏予。
Jvm內(nèi)存模型
方法區(qū)和堆是所有線程共享的內(nèi)存區(qū)域。java虛擬機(jī)棧弧轧,本地方法棧和程序計數(shù)器是線程私有的內(nèi)存區(qū)域雪侥。其中方法區(qū)和堆是垃圾回收的主要區(qū)域,java虛擬機(jī)棧劣针,本地方法和程序計數(shù)器不會發(fā)生垃圾回收校镐。
堆:是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,Java堆是被所有線程共享的一塊內(nèi)存區(qū)域捺典,在虛擬機(jī)啟動時創(chuàng)建鸟廓,此區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存襟己。
方法區(qū):方法區(qū)和堆一樣都是線程共享的區(qū)域引谜,它用于存儲已被虛擬機(jī)加載的類信息,常量擎浴,靜態(tài)變量员咽,即是編譯器編譯后的代碼等數(shù)據(jù)。
java虛擬機(jī)棧:是棧線程私有的贮预,它的生命周期與線程相同贝室,虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型。每一個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀仿吞,用于存儲局部變量表滑频,操作棧,動態(tài)鏈接唤冈,方法出口等信息峡迷。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
本地方法棧:本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的绘搞,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行的Java方法服務(wù)彤避,而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。
程序計數(shù)器:是一塊是較小的內(nèi)存空間夯辖,它可以看作當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器琉预。字節(jié)碼解析器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,每一個線程都需要一個獨(dú)立的程序計算器蒿褂,各個線程之間計數(shù)器互不影響模孩,獨(dú)立存儲。
Jvm的垃圾回收算法
哪些要回收贮缅?如何判斷對象是否是死忙榨咐?
引用計數(shù)算法
對象中添加一個引用計數(shù)器,每當(dāng)一個地方引用它時谴供,計數(shù)器值就加一块茁;當(dāng)引用失效時,計數(shù)器值就減一桂肌;任何時刻計數(shù)器為零的對象就是不可能再被使用的数焊。
可達(dá)性分析算法
可達(dá)性分析算法的基本思路是通過一系列為"GC Roots" 的根對象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開始崎场,根據(jù)引用關(guān)系向下搜索佩耳,通過搜索過程所走過的路徑稱為“引用鏈”,如果這個對象到GC Roots間沒有任何引用鏈相連谭跨,或者用圖論的話就是從GC Roots到這個對象不可達(dá)時干厚,則證明此對象不可能再被使用的。
可以做為GC Roots的對象
- 在虛擬機(jī)棧中引用的對象螃宙,譬如各個線程被調(diào)用的方法堆蛮瞄,棧中使用帶的參數(shù)、局部變量谆扎、臨時變量等挂捅。
- 在方法區(qū)中類型靜態(tài)屬性引用的對象,譬如Java類的引用類型靜態(tài)變量堂湖。
- 在方法區(qū)中引用的對象闲先,譬如字符串常量池里的引用。
- 在本地方法棧中JNI引用的對象无蜂。
- Java虛擬機(jī)內(nèi)部的引用伺糠,如基本數(shù)據(jù)可惜對應(yīng)的Class對象,一些常駐的異常對象等酱讶。
- 所有被同步鎖持有的對象退盯。
垃圾回收算法
標(biāo)記-清除算法
首先標(biāo)記出所有需要回收的對象彼乌,在標(biāo)記完成后泻肯,統(tǒng)一回收掉所有被標(biāo)記的對象渊迁,也可以反過來,標(biāo)記存活的對象灶挟,統(tǒng)一回收所有未被標(biāo)記的對象琉朽。這個算法有兩個主要的缺點(diǎn):第一個是執(zhí)行效率不穩(wěn)定,如果Java堆中包含有大量的對象稚铣,其中大部分是需要被回收的箱叁,這是必須進(jìn)行大量標(biāo)記和清除的動作,導(dǎo)致標(biāo)記和清除兩個過程的執(zhí)行效率都隨對象數(shù)量增長而降低惕医;第二個是內(nèi)存空間的碎化問題耕漱,標(biāo)記、清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片抬伺,空間碎片太多可能會導(dǎo)致當(dāng)以后在程序運(yùn)行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作螟够。
標(biāo)記-復(fù)制算法
它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊峡钓,這一塊的內(nèi)存用完了妓笙,就將還存活的著的對象復(fù)制到另一塊上面,然后再把自己使用過的內(nèi)存空間一次清理掉能岩。如果內(nèi)存中多數(shù)對象都是存活的寞宫,這種算法將會產(chǎn)生大量的內(nèi)存復(fù)制的開銷,但是對于大多數(shù)對象都是可回收的情況拉鹃,算法需要復(fù)制的就是占少數(shù)的存活對象辈赋,而且每次都是針對整個半?yún)^(qū)進(jìn)行內(nèi)存回收,分配內(nèi)存時也就不需要考慮空間碎片的復(fù)雜情況膏燕。但是這種回收算法的代價是將可用內(nèi)存縮小為了原本的一半炭庙,造成空間的浪費(fèi)。適合用于新生代煌寇。
標(biāo)記-整理算法
首先標(biāo)記處所有需要回收的對象焕蹄,在標(biāo)記完成后,讓所有存活的對象都向內(nèi)存空間的一端移動阀溶,然后直接清理掉邊界以外的內(nèi)存腻脏。適合于老年代對象。
類加載過程
類加載的定義:
Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件
加載到內(nèi)存银锻,并對數(shù)據(jù)進(jìn)行校驗永品,轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型击纬,這個過程被稱作為虛擬機(jī)的類加載機(jī)制鼎姐。
類加載的過程:
- 加載
在加載階段,虛擬機(jī)主要完成以下三件事情:
- 通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象炕桨,作為這個類的各種數(shù)據(jù)的訪問入口饭尝。
- 驗證
驗證是連接階段的第一步,目的是確保Class文件的字節(jié)流中包含的信息符合虛擬機(jī)的全部約束要求献宫,保證這些信息被當(dāng)作代碼運(yùn)行后不會危害虛擬機(jī)的安全钥平。驗證字節(jié)碼是Java虛擬機(jī)保護(hù)自身的一項必要措施。驗證階段非常必要姊途,這個階段是否嚴(yán)謹(jǐn)涉瘾,直接決定了Java虛擬機(jī)是否能承受惡意代碼的攻擊。驗證階段主要是對文件格式驗證捷兰,元數(shù)據(jù)驗證立叛,字節(jié)碼驗證和符號引用驗證。
- 準(zhǔn)備
準(zhǔn)備階段是正式類中定義的變量贡茅,即靜態(tài)變量分配內(nèi)存并設(shè)置變量初始化值的階段囚巴。需要注意的是初始值有以下兩種情況:
public static int value=123;
public static final inr value=123;
第一行代碼在準(zhǔn)備階段過后的賦值是0而不是123,因為這時尚未開始執(zhí)行任何Java方法友扰,如果被final修飾之后彤叉,在準(zhǔn)備階段就會根據(jù)ConstantValue屬性所指定的初始值。
- 解析
解析階段是Java虛擬機(jī)將常量池的符號引用替換為直接引用的過程村怪。
- 符號引用:符號引用以一組符號來描述所引用的目標(biāo)秽浇,符號可以是任何形式的字面量,只要使用時無歧義地定位到目標(biāo)即可甚负。符號引用與虛擬機(jī)的內(nèi)存布局無關(guān)柬焕,引用的目標(biāo)不一定是已經(jīng)加載到虛擬機(jī)內(nèi)存當(dāng)中的內(nèi)容。各種虛擬機(jī)實現(xiàn)的內(nèi)存布局可以各不相同梭域,但是它們能接受的符號引用必須都是一直的斑举。因為符號引用的字面量形式明確定義在《Java虛擬機(jī)規(guī)范》的Class文件格式中。
- 直接引用是可以直接指向目標(biāo)的指針病涨,相對偏移量或者一個能簡接定位到目標(biāo)的句柄富玷。直接引用是和虛擬機(jī)實現(xiàn)內(nèi)存布局直接相關(guān)的,同一個符號引用在不同虛擬機(jī)實例上翻譯出來的 直接引用一般不會相同既穆。
- 初始化
類的初始化階段是類加載過程的最后一步驟赎懦,直到初始化階段,Java虛擬機(jī)才真正開始執(zhí)行類中編寫的Java程序代碼幻工,將主導(dǎo)權(quán)移交給應(yīng)用程序励两。在準(zhǔn)備階段時,變量已經(jīng)賦過一次系統(tǒng)要求的初始值囊颅,而初始化階段当悔,則會根據(jù)程序通過程序編碼定制的主觀計劃去初始化類變量和其他資源傅瞻。直觀表達(dá)就是初始化階段就是執(zhí)行類構(gòu)造器<clinit>()方法的過程,主要完成靜態(tài)代碼塊執(zhí)行以及靜態(tài)變量的賦值。
類加載器
對于任意一個類盲憎,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機(jī)中的唯一性嗅骄,每一個類加載器,都擁有一個獨(dú)立的類名稱空間焙畔。 可以參考https://blog.csdn.net/m0_38075425/article/details/81627349
類加載器的介紹
站在Java虛擬機(jī)的角度看,只存在兩種不同的類加載器:一種是啟動類加載器串远,這個類加載器使用C++語言實現(xiàn)宏多,是虛擬機(jī)自身的一部分;另外一種就是其他所有的類加載器澡罚,這些類加載器都由Java語言實現(xiàn)伸但,獨(dú)立存在虛擬機(jī)外部,并且全都繼承自抽象類java.lang.ClassLoader留搔。在JDK8更胖,之前一直都是使用三層類加載器。
- 啟動類加載器:這個兩類加載器負(fù)責(zé)加載存放在<JAVA_HOME>\lib目錄隔显,或者被-Xbootclasspath參數(shù)所在指定的路徑中存放的却妨,而且是Java虛擬機(jī)能夠識別的類加載到虛擬機(jī)的內(nèi)存中。
- 擴(kuò)展類加載器:這個類加載器是在類sun.misc.Launcher$ExtClassLoader中以Java代碼的形式實現(xiàn)的括眠。它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中彪标,或者被java.ext.dirs系統(tǒng)變量所指定的路經(jīng)中所有的類庫。
- 應(yīng)用程序類加載器:這個類加載器由sun.misc.Launcher$AppClassLoader來實現(xiàn)掷豺。由于程序類加載器是ClassLoader類中的getSystemClassLoader()方法的返回值捞烟,所以有些場合中也被它稱為“系統(tǒng)類加載器”。
雙親委派模型
類加載器之間的父子關(guān)系一般不是以繼承的關(guān)系來實現(xiàn)当船,而是通常使用組合關(guān)系來復(fù)用父類加載器的代碼题画。
雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類德频,而是把這個請求委派給父類加載器去完成苍息,每一個層次的類加載器都是如此,因此所有的加載請求最終都是應(yīng)該傳送到最頂層的啟動類加載器中壹置,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個加載請求時档叔,子加載器才會嘗試自己去完成加載。
雙親委派加載器的優(yōu)勢就是:Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系蒸绩。例如類Java.lang.Object,它存放在rt.jar之中衙四,無論哪一個類加載器需要加載這個類,最終都是委派給處于模型最頂端的啟動類加載器進(jìn)行加載患亿,因此Object類在程序的各種類加載器環(huán)境中都能夠保證是同一個類传蹈。
Java內(nèi)存模型
- 主要目的
Java內(nèi)存模型的主要目的是定義程序中各種變量的訪問規(guī)則押逼,即關(guān)注在虛擬機(jī)中把變量值存儲到內(nèi)存和從內(nèi)存中取出來變量值這樣的底層細(xì)節(jié)。此處的變量與Java編程中所說的變量有所區(qū)別惦界,它包括了實例字段挑格,靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但是不包括局部變量和方法參數(shù)沾歪,因為后者是線程私有的漂彤,不會被共享,自然就不存在競爭的問題灾搏。
Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中挫望。每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存保存了被線程使用的變量的主存的副本狂窑,線程對變量的所有操作都必須都是在工作內(nèi)存中進(jìn)行的媳板,而不能直接讀寫主內(nèi)存中數(shù)據(jù)。不同線程之間無法直接訪問對方工作內(nèi)存的對象泉哈,線程間變量值的傳遞均需要通過主存來完成蛉幸。
- 原子性
原子性是指一個操作是不可中斷的丛晦,要么全部執(zhí)行成功要么全部執(zhí)行失敗奕纫,在多線程中一個操作一旦開始,就不會被其他線程所干擾烫沙。但是volatile不能保證原子性若锁。
- 有序性:
可以總結(jié)為一句話:如果在本地線程內(nèi)觀察,所有操作都是有序的斧吐;如果在一個線程中觀察另一個線程又固,所有線程都是無序的。前半句是指“線程內(nèi)是表現(xiàn)為串行的意義”后半句是指“指令重排序”的現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象煤率。Volatile關(guān)鍵字本身包含了禁止指令重排序的語義仰冠。而sychronied則是由“一個變量在同一時刻只能允許一條線程對其進(jìn)行l(wèi)ock操作〉矗”
禁止指令重排序是指:能夠保證變量的賦值操作的順序與程序代碼中的執(zhí)行順序一致洋只。而普通變量僅保證在改方法執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果。
- 可見性
可見性是指當(dāng)前一個線程修改了共享變量的值時昼捍,其他線程能夠立即得知這個修改识虚。Java內(nèi)存模型是通過在遍歷修改后將新值同步到主內(nèi)存中,在變量讀取前從主內(nèi)存中刷新變量值妒茬。Volatile保證了新值能夠立即同步到主內(nèi)存中担锤,以及每次使用前立即從主內(nèi)存中刷新。sychronied的可見性是由“對一個變量執(zhí)行unlock操作之前乍钻,必須先把此變量同步回主內(nèi)存中肛循。final的可見性是指:被final修飾的字段在構(gòu)造器中一旦被初始化完成铭腕,并且構(gòu)造器沒有把"this"的引用傳遞出去,那么其他線程中就能看見final字段的值多糠。
可重入性是指當(dāng)線程請求自己持有的對象鎖時累舷,如果請求成功,則說明是可重入鎖夹孔。sychronied是一個可重入鎖被盈,因此在一個線程使用sychronized方法時調(diào)用該對象另一個sychronied方法,即一個線程得到一個對象鎖后再次請求該對象鎖是永遠(yuǎn)可以獲得鎖的搭伤。
synchronized可重入鎖的實現(xiàn)
每個鎖關(guān)聯(lián)一個線程持有者和一個計數(shù)器只怎,當(dāng)計數(shù)器為0表示該鎖沒有被任何線程持有,那么任何線程都可能獲得該鎖而調(diào)用相應(yīng)的方法闷畸。當(dāng)一個線程獲得鎖后尝盼,Jvm會記下持有鎖的線程吞滞,并將計數(shù)器為1佑菩。此時其他線程請求該鎖,則必須等待裁赠,而持有該鎖的線程如果再次請求這個鎖殿漠,可以再次拿到,同時計算器會遞增佩捞。當(dāng)線程退出一個sychronied方法/塊時绞幌,計數(shù)器就會遞減,如果計數(shù)器為0則釋放該鎖一忱。