學習筆記-(java虛擬機)
時間:2023-06-26 03:09:01 | 來源:網(wǎng)站運營
時間:2023-06-26 03:09:01 來源:網(wǎng)站運營
學習筆記-(java虛擬機):
JVM定義及作用:
JVM是java Virtual Machine (java 虛擬機),是一個虛擬計算機,也是java程序是愛杭州跨平臺的基礎。其作用是加載Java程序,將字節(jié)碼翻譯成機器碼再交給CPU執(zhí)行的虛擬計算機。
JVM的主要組成:
- 類加載器(ClassLoader)
- 運行時數(shù)據(jù)區(qū)(Runtime Data Area)
- 執(zhí)行引擎(Execution Engine)
- 本地庫接口(Native Interface)
工作流程:
首先程序在執(zhí)行前將java代碼(.java)轉(zhuǎn)變成字節(jié)碼(.class),JVM通過;類加載器將字節(jié)碼加載到內(nèi)存中,由于字節(jié)碼文件是JVM的一套指令集規(guī)范,并不能直接交給底層操作系統(tǒng)執(zhí)行,因此需要特定的命令解析器執(zhí)行引擎將字節(jié)碼翻譯成底層的機器碼,再交給CPU執(zhí)行,CPU執(zhí)行過程中需要調(diào)用本地庫接口來完成整個程序的運行。
JVM的內(nèi)存布局:
不同的虛擬機存在不同,但都會遵循java虛擬機規(guī)范。java 8虛擬機規(guī)范規(guī)定,內(nèi)存布局包括以下幾個區(qū)域:
- 程序計數(shù)器(Program Counter Register)
- java虛擬機棧(java Virtual Machine Stacks)
- 本地方法棧(Native Method Stack)
- java堆(java Heap)
- 方法區(qū)(Method Area)
1.程序計數(shù)器記錄正在執(zhí)行的虛擬機字節(jié)碼指令的地址(如果正在執(zhí)行的是native方法,該計數(shù)器的值為undefined)。此內(nèi)存區(qū)域是唯一一個在java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
線程私有。由于java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式實現(xiàn)的。為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要一個獨立的程序計數(shù)器,各線程之間的計數(shù)器互不影響,獨立存儲。
2.java虛擬機棧每個java方法在執(zhí)行的同時會創(chuàng)建一個棧幀用來存儲
局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。從方法調(diào)用直至執(zhí)行完成的過程,對應著一個棧幀在java虛擬機棧中入棧和出棧的過程。線程私有,生命周期與線程相同。
該區(qū)域有兩種異常:
- StackOverflowError:當線程請求的棧深度超過最大值
- OutOfMemoryError:虛擬機棧擴展到無法申請足夠的內(nèi)存時
3.本地方法棧類似于java虛擬機棧,區(qū)別是本地方法棧服務于本地方法。
4.java堆java堆是java虛擬機中內(nèi)存最大的一塊。在虛擬機啟動時創(chuàng)建,被所有線程共享。
存放對象實例。垃圾收集器主要管理的就是java堆。
現(xiàn)在的垃圾收集器基本采用分代收集算法,主要思想是針對不同類型的對象采取不同的垃圾回收算法,java堆可以分為兩塊:
- 新生代(Yong Generation)
- 老年代(Old Generation)
動態(tài)擴展內(nèi)存失敗會拋出
OutOfMemoryError異常。
5.方法區(qū)被所有線程共享,用于存放已被加載的
類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等信息。
動態(tài)擴展內(nèi)存失敗會拋出
OutOfMemoryError異常。
運行時常量池是方法區(qū)的一部分。保存Class文件中的符號引用、翻譯出來的直接引用。
垃圾收集
垃圾收集主要針對java堆和方法區(qū)。
1.判斷一個對象是否可被回收- 引用計數(shù)算法:給對象添加引用計數(shù)器,增加引用+1,引用失效-1。引用計數(shù)為0的對象可以被回收。(容易出現(xiàn)循環(huán)引用問題)
- 可達性分析算法:以GC Roots為起點進行搜索,可達的對象是存活的,不可達的對象可被回收。GC Roots 一般包括:
- 虛擬機棧中局部變量表中引用的對象
- 本地方法棧中JNI(java本地接口)中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
2.java的4中引用方式- 強引用(Strong Reference):被強引用關聯(lián)的對象不會被回收。 Object obj=new Object();
- 軟引用(Soft Reference):被軟引用關聯(lián)的對象只有在內(nèi)存不夠的情況下被回收。
- 弱引用(Weak Reference):被弱引用關聯(lián)的對象只能活到下一次垃圾回收發(fā)生之前。
- 虛引用(Phantom Reference):為對象設置虛引用的目的是在這個對象被回收時收到一個系統(tǒng)通知。
3.垃圾回收算法- 標記-清除算法(Mark-Sweep):首先標記處所有需要回收的對象,之后統(tǒng)一回收被標記的對象。效率低,標記清除后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片。
- 復制算法(Copying)-新生代:將可用內(nèi)存劃分成大小相等的兩塊,每次只使用一塊。當這塊內(nèi)存用完,將存活的對象復制到另一塊,飯后將使用過的內(nèi)存空間一次清理。
- 現(xiàn)在的商業(yè)虛擬機都采用這種復制算法回收新生代,但是并不是劃分為大小相等的兩塊,而是一塊較大的 Eden 空間和兩塊較小的 Survivor 空間(8:1:1),每次使用 Eden 和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的對象全部復制到另一塊 Survivor 上,最后清理 Eden 和使用過的那一塊 Survivor。
- 標記-整理算法(Mark-Compact)-老年代:讓所有存活的對象向一端移動,然后直接清理掉邊界外的內(nèi)存。
- 分代收集算法:根據(jù)對象的存貨周期,將內(nèi)存劃分為幾塊。一般將java堆劃分為新生代和老年代。
- 新生代:復制算法
- 老年代:標記-清除算法 / 標記-整理算法
垃圾收集器
- Serial 收集器:這是一個以串行的方式單線程收集器。它只會使用一個CPU或一條收集線程去完成收集工作,并且在進行垃圾回收是必須暫停其他所有工作線程直到收集結束。
- ParNew收集器:Serial收集器的多線程版本。
- 并行:Parallel指多條垃圾收集線程并行工作,此時用戶線程處于等待狀態(tài)。
- 并發(fā):Concurrent指用戶線程和垃圾回收線程同時執(zhí)行,用戶進程在執(zhí)行,而垃圾回收線程在另一個CPU上執(zhí)行。
- Parallel Scavenge收集器:并行的多線程收集器,使用復制算法。吞吐量優(yōu)先收集器。
- GC自適應調(diào)整策略:不需要手動指定新生代的大小及比例、晉升老年代的年齡等參數(shù),虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間、最大的吞吐量。
- Serial Old收集器:使用標記-整理算法
- Parallel Old收集器:使用標記-整理算法
- CMS(Concurrent Mark Sweep)收集器:是一種以獲取最短回收停頓時間為目標的收集器。基于標記-清除算法實現(xiàn)。
- 初始標記(CMS initial mark):標記GC Roots能直接關聯(lián)到的對象,停頓
- 并發(fā)標記(CMS concurrent mark):進行GC Roots Tracing ,不用停頓
- 重新標記(CMS remark):修正并發(fā)標記期間的變動部分,停頓
- 并發(fā)清除(CMS concurrent sweep):不需要停頓
- G1收集器:面向服務端的垃圾回收器。并行與并發(fā)、分代回收、空間整合、可預測停頓、G1將堆劃分成多個大小相等的獨立區(qū)域(Region)。
- 初始標記(initial mark)
- 并發(fā)標記(concurrent mark)
- 最終標記(final mark)
- 篩選回收(live data counting and evacuation)
Minor GC 和Full GC:
- Minor GC:回收新生代,新生代對象存活時間短,因此Minor GC會頻繁執(zhí)行,速度快。
- Full GC:回收老年代和新生代,老年代對象存活時間長,因此Full GC很少執(zhí)行,速度慢。
Full GC的觸發(fā)條件
- 調(diào)用System.gc():不建議使用
- 老年代空間不足
- 空間分配擔保失敗
- JDK1.7及之前的永久代空間不足
- Concurrent Mode Failure (CMS GC工程中老年代空間不足)
內(nèi)存分配策略
- 對象優(yōu)先在Eden分配
- 大對象直接進入老年代
- 長期存活的對象進入老年代
- 動態(tài)對象年齡判定:在Survivor中相同年齡所有對象總和大于Survivor空間的一半,則年齡大于等于該年齡的對象直接進入老年代。
- 空間分配擔保:在發(fā)生Minor GC之前,虛擬機先檢查老年代最大可用連續(xù)空間是否大于新生代所有對象總空間,如果條件成立,Minor GC確認安全;如果不成立,虛擬機查看HandlePromotionFailure的值是否允許擔保失敗,如果允許繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于嘗試進行Minor GC;如果小于或不允許冒險,就進行Full GC。
類加載器
類是在運行期間第一次使用時動態(tài)加載的,而不是一次性加載所有類。
類加載過程包括
加載、驗證、準備、解析、初始化這5個階段。
1.加載
- 通過類的完全限定名稱獲取定義該類的二進制字節(jié)流。
- 將該字節(jié)流表示的靜態(tài)存儲結構轉(zhuǎn)換為方法區(qū)的運行時存儲結構。
- 在內(nèi)存中生成一個代表該類的Class對象,作為方法區(qū)中該類各種數(shù)據(jù)的訪問入口。
2.驗證
確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并不會威海虛擬機的安全。
3.準備
- 類變量是被static修飾的變量,準備階段為類變量分配內(nèi)存并設置初始值,使用的是方法區(qū)的內(nèi)存。
- 實例變量不會在該階段分配內(nèi)存,它會在對象實例化時隨著對象一起被分配在堆中。
4.解析
虛擬機將常量池中的符號引用替換為直接引用。
5.初始化
開始執(zhí)行類中定義的java程序代碼。初始化階段是虛擬機執(zhí)行類構造器<clinit>()方法的過程。
雙親委派模型
如果一個類加載器收到一個類加載的請求,首先將類加載請求轉(zhuǎn)發(fā)到父類加載器,只有當父類加載器無法完成時才嘗試自己加載。
java內(nèi)存模型
并發(fā)注意操作的
原子性、可見性、有序性。- 原子性:基本數(shù)據(jù)類型的操作是原子性的。同時 lock 和 unlock 可以保證更大范圍操作的原子性。而 synchronize 同步塊操作的原子性是用更高層次的字節(jié)碼指令 monitorenter 和 monitorexit 來隱式操作的。
- 可見性:是指當一個線程修改了共享變量的值,其他線程也能夠立即得知這個通知。主要操作細節(jié)就是修改值后將值同步至主內(nèi)存(volatile 值使用前都會從主內(nèi)存刷新),除了 volatile 還有 synchronize 和 final 可以保證可見性。
- 有序性:線程內(nèi)表現(xiàn)為串行的語義,“指令重排”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。Java 語言通過 volatile 和 synchronize 兩個關鍵字來保證線程之間操作的有序性。
volatile
關鍵字volatile是java虛擬機提供的最輕量級的同步機制。
happens-before(先行發(fā)生原則)
這個原則是判斷數(shù)據(jù)是否存在
競爭、線程是否
安全的主要依據(jù)。先行發(fā)生是 Java 內(nèi)存模型中定義的兩項操作之間的
偏序關系。
java線程
線程狀態(tài)轉(zhuǎn)換- 新建(new):創(chuàng)建后尚未啟動。調(diào)用start()方法開始運行。
- 運行(Runnable):包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready,正在執(zhí)行或等待CPU分配時間。
- 無限期等待(Waiting):這種狀態(tài)下的線程不會被CPU分配時間, 等待其他線程顯示喚醒。以下方法會使線程進入無限期等待狀態(tài):
- 沒有設置Timeout參數(shù)的Object.wait()方法;
- 沒有設置Timeout參數(shù)的Thread.join()方法;
- LookSupport.park()方法。
- 限期等待(Timed Waiting):在一定時間后由系統(tǒng)自動喚醒。以下方法會使線程進入限期等待狀態(tài):
- Thread.sleep()方法;
- 設置了Timeout參數(shù)的Object.wait()方法;
- 設置了Timeout參數(shù)的Thread.join()方法;
- LookSupport.parkNanos()方法
- LookSupport.parkUntil()方法。
- 阻塞(Blocked):阻塞狀態(tài)是在等待獲取一個排他鎖,這個事件會在另一個線程放棄這個鎖的同時發(fā)生。在程序等待進入同步區(qū)域的時候,線程進入阻塞狀態(tài)。
- 結束(Terminated):已終止線程的線程狀態(tài)。
參考資料: