學習筆記(不定時更新))
Java虛擬機JVM學習筆記不少組織都曾開發(fā)過Java虛擬機SUN公司曾經(jīng)使用過3個虛擬機Classic、Exact VM、Hotspot。其中Hotspot虛擬機沿用至今并已被Oracle合并到其新的虛擬機平臺中與同樣被Oracle收購的BEA公司的JRocket虛擬機合并。IBM的Java虛擬機叫J9。Apache也曾推出過一款叫做Apache Harmony的Java運行平臺受到同樣開源的OpenJDK的排擠已于2011年退出市場。Java虛擬機的基本結構1、類加載子系統(tǒng)2、Java內(nèi)存程序計數(shù)器、Java堆、Java棧、本地方法棧、方法區(qū)、直接內(nèi)存3、垃圾回收子系統(tǒng)4、執(zhí)行引擎。Java堆一個Java程序的幾乎所有對象都存放在堆中并且堆是完全自動化管理的不需要顯式釋放堆中的垃圾對象。在不同的虛擬機中或在不同的垃圾回收機制下Java堆可能具有不同的結構特征但最常見的組織結構是把整個堆分為新生代和老年代tenured。新生代中存放新創(chuàng)建的對象和“年齡”不大的對象老年代則存放“年齡”足夠大的對象。新生代又分為eden、from、to三個區(qū)域其中from和to是兩塊大小相等、可以互換角色的內(nèi)存空間。通常新創(chuàng)建的對象在eden區(qū)分配內(nèi)存在一次新生代回收后如果對象還存活則會被轉(zhuǎn)移到from區(qū)或to區(qū)。對象進入from區(qū)或to區(qū)之后沒經(jīng)過一次新生代回收如果對象還存活著則它的“年齡”加1。當“年齡”增長到一定程度后會被轉(zhuǎn)移到老年代。Java棧Java棧是線程私有的內(nèi)存空間。函數(shù)調(diào)用是線程執(zhí)行的基本行為每次函數(shù)調(diào)用所需的數(shù)據(jù)都是通過Java棧傳遞的。Java棧和數(shù)據(jù)結構課程中所描述的棧有類似的特征都是一種先進后出的數(shù)據(jù)結構只支持入棧和出棧兩種操作。棧幀是Java棧的基本單元。每次函數(shù)調(diào)用都會有一個棧幀入棧每次函數(shù)調(diào)用結束都會有一個棧幀出棧。無論函數(shù)是正常返回還是拋出異常都會有一個棧幀出棧。棧幀在一個棧幀中至少包含3個部分局部變量表、操作數(shù)棧、幀數(shù)據(jù)區(qū)。由于每次函數(shù)調(diào)用都會有棧幀入棧從而占據(jù)更多的棧空間顯然如果棧空間不足以放置更多的棧幀時函數(shù)調(diào)用將無法繼續(xù)下去這個時候會拋出StackOverflowError。Java虛擬機提供了“-Xss”參數(shù)用于在虛擬機啟動的時候配置棧空間的最大值這個最大值決定了函數(shù)嵌套調(diào)用的深度。局部變量表局部變量表是棧幀的重要組成部分它用于保存函數(shù)的參數(shù)和局部變量。由于局部變量表在棧幀中如果函數(shù)的參數(shù)和局部變量過多會造成局部變量表膨脹從而棧幀變大函數(shù)嵌套調(diào)用的深度也會降低。操作數(shù)棧操作數(shù)棧也是棧幀的組成部分之一它被用來保存計算的中間結果作為計算過程中變量的臨時存儲空間。操作數(shù)棧也是先進后出的很多Java字節(jié)碼指令都需要通過操作數(shù)棧進行參數(shù)傳遞。幀數(shù)據(jù)區(qū)幀數(shù)據(jù)區(qū)中保存著常量池的指針方便字節(jié)碼指令訪問常量池。幀數(shù)據(jù)區(qū)中保存了一張異常處理表方便在發(fā)生異常時找到處理異常的代碼。棧上分配棧上分配是Java虛擬機提供的優(yōu)化技術。它的思路是可以把某些線程私有對象分配在棧上借助退棧操作直接清理該對象而不需要垃圾回收子系統(tǒng)的介入減少GC從而提高系統(tǒng)性能。棧上分配的技術基礎是逃逸分析逃逸分析用于判斷某個對象是否能夠被其他線程訪問到。如果某個對象能夠被其他線程訪問到則它是一個逃逸對象不能分配在棧上。順便提一下書上說只有Java虛擬機運行在Server模式下的時候才可以啟用逃逸分析。方法區(qū)方法區(qū)和Java堆一樣是線程共享的內(nèi)存區(qū)域。它主要用于保存Java類信息字段、方法等。方法區(qū)的大小決定了虛擬機中可以加載多少Java類。如果有太多的Java類需要加載通常使用了某些動態(tài)代理技術時會動態(tài)產(chǎn)生過多的類信息會拋出內(nèi)存溢出錯誤。虛擬機參數(shù)-XX:PermSize和-XX:MaxPermSize用于指定方法區(qū)的最小和最大尺寸。默認情況下最大64MB。在JDK1.6和1.7中方法區(qū)也叫永久區(qū)。在JDK1.8、1.9和1.10中永久區(qū)已被“元數(shù)據(jù)區(qū)”取代元數(shù)據(jù)區(qū)是直接內(nèi)存。和永久區(qū)不同的是元數(shù)據(jù)區(qū)可以擴展至耗盡所有系統(tǒng)內(nèi)存。跟蹤調(diào)試參數(shù)Java虛擬機提供了大量用于跟蹤系統(tǒng)狀態(tài)的參數(shù)配置使用這些參數(shù)可以在Java程序運行過程中打印相關日志用于問題分析。GC參數(shù)GC垃圾回收機制是Java的一大特色可以幫助程序員管理內(nèi)存。但是當GC對程序本身造成影響GC過于頻繁、CPU過載等時需要進行分析處理。打印GC日志-XX:PrintGC使用這個參數(shù)啟動虛擬機只要遇到GC就會打印日志。-Xlog:gc這個參數(shù)是用來給JDK9和JDK10打印GC日志的因為這兩個版本的JDK默認使用G1收集器。打印詳細GC日志的參數(shù)是-XX:PrintGCDetailsJDK9和JDK10中對應的參數(shù)是-Xlog:gc*。跟蹤類的加載、卸載-verbose:class參數(shù)可以跟蹤類的加載和卸載。單獨使用-XX:TraceClassLoading參數(shù)JDK9和JDK10中對應的參數(shù)是-Xlog:classloadinfo跟蹤類的加載情況。單獨使用-XX:TraceClassUnloading參數(shù)JDK9和JDK10中對應的參數(shù)是-Xlog:classunloadinfo跟蹤類的卸載情況。查看系統(tǒng)參數(shù)-XX:PrintVMOptions參數(shù)在程序運行時打印虛擬機接收到的命令行顯式參數(shù)列表。-XX:PrintCommandLineFlags參數(shù)打印傳遞給虛擬機的顯式和隱式參數(shù)。-XX:PrintFlagsFinal參數(shù)打印所有的系統(tǒng)參數(shù)值。Java堆大小設置當Java進程啟動時虛擬機會給該進程分配一塊初始堆空間當初始堆空間耗盡后虛擬機會擴展堆空間擴展后的堆空間大小不能超過最大堆空間。-Xms參數(shù)在Java進程啟動時指定初始堆空間大小。-Xmx參數(shù)指定最大堆空間大小。新生代的配置-Xmn參數(shù)用于設置新生代的大小。-XX:SurvivorRatio參數(shù)用于指定新生代中eden區(qū)和from/to區(qū)的比例即-XX:SurvivorRatioeden/fromeden/to新生代的大小一般設置為整個堆空間的四分之一到三分之一新生代的大小對GC有很大影響基本上應該盡可能將對象預留在新生代以減少老年代GC的次數(shù)。設置較大的新生代會減小老年的大小。-Xmn參數(shù)可以指定新生代的絕對大小還可以使用-XX:NewRatio參數(shù)來設置新生代和老年代的比例-XX:NewRatio老年代/新生代堆溢出Java程序在運行過程中如果遇到堆內(nèi)存不足的情況時則會拋出內(nèi)存溢出錯誤OOMOut Of Memory。對于OOMJava虛擬機提供了-XX:HeapDumpOnOutOfMemoryError參數(shù)用于在發(fā)生OOM時導出整個堆信息。這個參數(shù)配合另一個參數(shù)-XX:HeapDumpPath指定導出堆的存放路徑java -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathd:/java.dump ...另外還有一個參數(shù)-XX:OnOutOfMemoryError在發(fā)生錯誤時調(diào)用一個腳本文件用于善后處理。java -XX:OnOutOfMemoryErrord:/abc.bat ...方法區(qū)配置在JDK1.6和JDK1.7中可以使用下面兩個參數(shù)設置方法區(qū)大小的下限和上限-XX:PermSize-XX:MaxPermSizeJDK1.8以后方法區(qū)已被廢棄類信息存放在元數(shù)據(jù)區(qū)中。元數(shù)據(jù)區(qū)只受系統(tǒng)可用內(nèi)存的限制。可以使用-XX:MaxMetaspaceSize參數(shù)設置元數(shù)據(jù)區(qū)的最大可用值。直接內(nèi)存配置直接內(nèi)存跳過Java堆直接操作系統(tǒng)原生內(nèi)存。從一定程度上加快了內(nèi)存的訪問速度。但是認為使用直接內(nèi)存一定可以提高性能是錯誤的。一般來說直接內(nèi)存的訪問速度比Java堆內(nèi)存要快但是在內(nèi)存申請時直接內(nèi)存毫無優(yōu)勢可言。所以看直接內(nèi)存適合申請次數(shù)較少、訪問較頻繁的場景。-XX:MaxDirectMemorySize參數(shù)可以限定直接內(nèi)存的最大值如果不設置這個參數(shù)則默認最大值為-Xmx參數(shù)的值。當已使用的直接內(nèi)存到達指定的最大值時會觸發(fā)GCGC后如果仍舊內(nèi)存不足會引起OOM。Java虛擬機的工作模式Java虛擬機支持Client和Server兩種運行模式。這兩種運行模式可以通過兩個參數(shù)分別指定-client參數(shù)-server參數(shù)因為以Server模式啟動Java程序時會嘗試手機更多的系統(tǒng)性能信息使用更復雜的優(yōu)化算法所以Server模式啟動程序的速度要慢于Client模式。但是也正是因為這樣以Server模式啟動的Java程序要遠遠快于以Client模式啟動的Java程序。常見垃圾回收算法1、引用計數(shù)法Reference Counting2、標記清除法Mark-Sweep3、復制算法Copying4、標記壓縮法Mark-Compact5、分代算法Generationall Collecting6、分區(qū)算法Region。引用計數(shù)法引用計數(shù)法是最經(jīng)典且古老的一種垃圾收集算法。引用計數(shù)法的基本思想是給對象設置一個引用次數(shù)的變量。對象被賦值給其他變量引用次數(shù)加一引用失效后引用次數(shù)減一。但引用次數(shù)變成零的時候代表對象不再被任何變量引用可以直接回收該對象占用的內(nèi)存空間。引用計數(shù)法的最嚴重問題是無法處理循環(huán)引用的問題。標記清除法標記清除法是現(xiàn)代垃圾收集算法的思想基礎。標記清除法分兩個階段標記階段和清除階段。在標記階段算法通過根節(jié)點標記所有從根節(jié)點開始的可達對象。在清除階段回收所有未被標記的對象所占用的內(nèi)存空間。標記清除法的最大問題是可能產(chǎn)生大量的內(nèi)存碎片。復制算法復制算法的核心思想是將這個內(nèi)存空間分成兩塊每次只使用其中的一塊。當需要垃圾回收時將正在使用的那一塊中所有的存活對象都復制到另一塊內(nèi)存空間中然后清理當前內(nèi)存塊中的所有對象。之后交換兩塊內(nèi)存的角色GC完成。復制算法非常符合新生代垃圾回收的要求因為新生代里的垃圾對象通常遠遠多于存活對象因此更有效率。之前介紹過新生代被分成了三塊eden、from、to。其中的from區(qū)和to區(qū)可以視為復制算法中的兩塊大小相同、地位相等且可以互換角色的內(nèi)存空間。標記壓縮法標記壓縮法是一種老年代的回收算法。它是標記清除法的優(yōu)化版。標記壓縮法在執(zhí)行了標記清除法的兩個階段后還會把存活對象壓縮到內(nèi)存的一端從而避免了碎片的產(chǎn)生。也就是說標記壓縮法標記清除法碎片整理。分代算法分代算法本身并不是一種新的垃圾回收算法。它的思路是根據(jù)對象的特點把內(nèi)存分成不同的塊然后根據(jù)每塊內(nèi)存中對象的特點選擇不同的垃圾回收算法以提高效率。分代算法被現(xiàn)代虛擬機廣泛使用幾乎所有的垃圾回收器都區(qū)分新生代和老年代。通常新生代會頻繁發(fā)生垃圾回收但是每次垃圾回收耗時都比較短而老年代的垃圾回收并不頻繁但是只要發(fā)生垃圾回收都會消耗很長時間。分區(qū)算法通常內(nèi)存空間越大一次垃圾回收的耗時就越長。分區(qū)算法的思路是把這個內(nèi)存空間分割成若干小的區(qū)間每次需要垃圾回收時都選擇其中的部分小區(qū)間進行回收而不是回收整個內(nèi)存空間。這樣就有效控制了每次垃圾回收所產(chǎn)生的停頓時間。可觸及性一個相對是否可以被當做垃圾回收需要考察這個對象的可觸及性即在任何情況下從根節(jié)點開始是否可以訪問到這個對象。如果在任何情況下從所有根節(jié)點開始都無法直接或間接訪問到這個對象則說明這個對象已經(jīng)不再被使用了該對象可以被回收。可觸及性設計三種狀態(tài)1、可觸及從根節(jié)點開始可以到達該對象則稱該對象可觸及2、可復活對象的所有引用都已釋放但是該對象可以通過調(diào)用其finalize方法被復活3、不可觸及finalize方法已被調(diào)用finalize只可能被虛擬機調(diào)用一次但沒有被復活該對象就進入不可觸及狀態(tài)。finalize方法finalize方法是一個非常糟糕的反模式不推薦使用它來釋放資源。首先finalize方法很可能在無意中復活對象。其次finalize方法是由系統(tǒng)調(diào)用的調(diào)用時機不明確。引用強度Java中提供了4個級別的引用1、強引用2、軟引用3、弱引用4、虛引用。強引用強引用就是一般的引用它不會被垃圾回收器回收。軟引用java.lang.ref.SoftReference軟引用比強引用稍微弱一點。如果某對象只持有軟引用那么當堆空間不足時就會被回收因此軟引用對象不會引起內(nèi)存溢出。弱引用java.lang.ref.WeakReference弱引用比軟引用還要弱。在垃圾回收時不管系統(tǒng)內(nèi)存的使用情況如何只要發(fā)現(xiàn)弱引用就會立即對其進行回收。弱引用之所以可以在內(nèi)存中存在是因為垃圾回收線程的優(yōu)先級很低不能立即發(fā)現(xiàn)它。虛引用java.lang.ref.PhantomReference虛引用是所有引用類型中最弱的一個。虛引用和沒有引用幾乎是一樣的隨時都可能被回收。試圖通過虛引用的get方法取得強引用時總會失敗。虛引用只是為了跟蹤垃圾回收過程。垃圾回收器1、串行回收器2、并行回收器3、CMS回收器4、G1回收器。串行回收器每次垃圾回收時串行回收器都只有一個工作線程。對于并行能力差的機器使用串行回收器反而性能更高、效率更好。因為機器并行能力差啟動多個線程并行回收反而多了線程切換的開銷。串行回收器是獨占式的垃圾回收過程中所有其他線程都要停止工作。串行回收器既可以用于新生代也可以用于老年代。新生代串行回收器新生代串行回收器使用復制算法容易實現(xiàn)、邏輯處理高效且沒有線程切換的開銷。虛擬機以Client模式啟動時串行回收器是默認回收器。老年代串行回收器老年代串行回收器使用標記壓縮算法。可以使用以下三種參數(shù)指明老年代使用串行回收器1、-XX:UseSerialGC參數(shù)新生代、老年代都使用串行回收器2、-XX:UseParNewGC參數(shù)JDK9和JDK10中該參數(shù)已作廢新生代使用ParNew回收器老年代使用串行回收器3、-XX:UseParallelGC參數(shù)指定新生代使用ParallelGC回收器老年代使用串行回收器。并行回收器并行回收器使用多個線程同時進行垃圾回收對于并行能力更強的機型來說可以有效減少每次垃圾回收所需的時間。新生代并行回收器ParNewParNew回收器是新生代垃圾回收器使用復制算法它只是簡單地將新生代串行回收器多線程化。ParNew回收器也是獨占式的當它啟動時其他所有線程都要暫停。-XX:UseConcMarkSweepGCJDK9、JDK10中已不建議再使用該參數(shù)新生代使用ParNew回收器老年代使用CMS回收器。ParNew回收器的線程數(shù)可以通過-XX:ParallelGCThreads參數(shù)指定。該回收器的線程數(shù)一般需要與計算機的CPU核數(shù)關聯(lián)一般情況下1、當CPU核數(shù)小于8時ParallelGCThreads的值取CPU的核數(shù)2、當CPU核數(shù)大于8時ParallelGCThreads的值等于3(5*CPU_count/8)。新生代并行回收器ParallelGCParallelGC回收器也使用復制算法。看上去ParallelGC回收器和ParNew回收器很相似。不同的是ParallelGC回收器非常關注系統(tǒng)的吞吐量。新生代ParallelGC回收器可以使用兩種參數(shù)啟用1、-XX:UseParallelGC新生代使用ParallelGC回收器老年代使用串行回收器。2、-XX:UseParallelOldGC新生代使用ParallelGC回收器老年代使用ParallelOldGC回收器。ParallelGC回收器提供了兩個參數(shù)用于控制系統(tǒng)吞吐量1、-XX:MaxGCPauseMillis設置最大停頓時間該參數(shù)值是一個大于0的整數(shù)。指定該參數(shù)后回收器會自動調(diào)整Java堆的大小或其他系統(tǒng)參數(shù)盡量把停頓時間控制在MaxGCPauseMillis以內(nèi)。MaxGCPauseMillis不能設置得過小因為過小的話回收器會把堆調(diào)整得很小這樣會造成垃圾回收頻繁系統(tǒng)吞吐量反而更低了。2、-XX:GCTimeRatio設置吞吐量大小該參數(shù)值是一個0到100以內(nèi)的整數(shù)。系統(tǒng)將花費不超過1/(1GCTimeRatio)的時間進行垃圾回收。默認GCTimeRatio到取值為99即有不超過1/(199)1%的時間用于垃圾回收。-XX:MaxGCPauseMillis參數(shù)和-XX:GCTimeRatio參數(shù)是互相矛盾的因為如果減小每次垃圾回收的時間吞吐量也會降低而增加吞吐量又會延長每次垃圾回收的時間。ParallelGC回收器還可以通過-XX:UseAdaptiveSizePolicy參數(shù)打開自適應GC策略讓虛擬機自行完成GC調(diào)優(yōu)工作。老年代并行回收器ParallelOldGCParallelOldGC回收器使用標記壓縮算法。ParallelOldGC回收器也是多線程的和新生代ParallelGC回收器一樣它也關注吞吐量。CMS回收器Concurrent Mark Sweep、并發(fā)標記清除CMS回收器使用標記清除算法。CMS回收器主要關注系統(tǒng)停頓時間。CMS回收器的工作過程CMS回收器工作時的主要步驟有1、初始標記獨占式2、并發(fā)標記3、預清理4、重新標記獨占式5、并發(fā)清除6、并發(fā)重置。啟用CMS回收器使用參數(shù)-XX:UseConcMarkSweepGC可以聲明啟用CMS回收器。并行和并發(fā)的區(qū)別并發(fā)是指回收器和應用線程交替執(zhí)行并行是指應用程序完全掛起同時由多個線程一起執(zhí)行GC。CMS回收器的執(zhí)行CMS回收器不是獨占式的在CMS回收過程中應用線程任然可以繼續(xù)執(zhí)行。應用線程執(zhí)行過程中又會產(chǎn)生垃圾對象這些新的垃圾對象在當前的CMS回收過程中是無法清理的。所以在CMS回收過程中要確保應用線程有足夠的內(nèi)存可用。因此CMS回收器不會等到內(nèi)存耗盡的時候才進行垃圾回收而是在內(nèi)存使用率到達一定程度時就開始垃圾回收了。可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction指定一個閾值默認68即內(nèi)存使用率到達68%時CMS回收器就會執(zhí)行。CMS閾值的設置原則如果內(nèi)存使用率飛速增長在CMS回收過程中已經(jīng)出現(xiàn)了內(nèi)存不足的狀況CMS回收就會失敗。CMS失敗后虛擬機會啟動老年代串行回收器進行垃圾回收這時應用線程會被全部掛起造成很長的停頓時間。如果內(nèi)存使用率上升緩慢可以將CMSInitiatingOccupancyFraction設大點以有效降低CMS的觸發(fā)頻率減少老年代回收次數(shù)。如果內(nèi)存使用率上升飛快可以將CMSInitiatingOccupancyFraction設小點以避免老年代串行回收器的觸發(fā)頻率。CMS內(nèi)存壓縮參數(shù)CMS回收器使用的是標記清除算法這種算法容易產(chǎn)生內(nèi)存碎片。為了減少碎片CMS回收器提供了兩個用于壓縮內(nèi)存的參數(shù)1、-XX:UseCMSCompactAtFullCollectionCMS垃圾收集完成后進行一次碎片整理非并發(fā)。2、-XX:CMSFullGCBeforeCompaction設定進行了多少次CMS回收后進行一次內(nèi)存壓縮。G1回收器Garbage FirstG1回收器是JDK1.7中引入的可以替代CMS回收器。G1仍舊屬于分代回收器。從堆的結構上看G1回收器采用了分區(qū)算法新生代和老年代都不必連續(xù)。G1兼顧新生代和老年代其它回收器要么只工作在新生代要么只工作在老年代。G1每次回收都會進行有效的內(nèi)存復制減少內(nèi)存碎片。由于分區(qū)G1可以每次只回收部分區(qū)塊縮小了回收范圍可以有效控制GC停頓。G1相關參數(shù)-XX:UseG1GC該參數(shù)用于聲明使用G1回收器。-XX:MaxGCPauseMillis該參數(shù)用于指定最大停頓時間。在實際運行過程中如果任何一次停頓超過MaxGCPauseMillisG1就會調(diào)整新生代和老年代的比例、調(diào)整堆大小、調(diào)整晉升年齡等以符合預設目標。-XX:InitiatingHeapOccupancyPercent這個參數(shù)用于指定當整個堆使用率達到多少時觸發(fā)并發(fā)標記周期。InitiatingHeapOccupancyPercent默認值是45即當整個堆的使用率達到45%時觸發(fā)并發(fā)標記周期。InitiatingHeapOccupancyPercent一旦被設置G1就不會再修改它了。禁用System.gc()默認情況下System.gc()會顯式觸發(fā)Full GC同時對老年代和新生代進行回收。使用-XX:-DisableExplicitGC可以禁用顯式GC它使System.gc()等價于一個空函數(shù)調(diào)用。調(diào)用System.gc()時使用并發(fā)回收默認情況下如果沒有禁用System.gc()會使用傳統(tǒng)的Full GC方式回收整個堆。使用參數(shù)-XX:ExplicitGCInvokesConcurrent可以使用并發(fā)方式進行回收。不使用這個參數(shù)即使啟用了CMS或G1也不會進行并發(fā)回收。TLAB線程本地分配緩存Thread Local Allocation BufferTLAB是線程專屬的內(nèi)存分配區(qū)域。在同一時刻通常會有大量的線程在對上申請內(nèi)存空間所以每一次內(nèi)存分配都必須進行加鎖同步加鎖是效率低下的。TLAB由于是線程專屬的其他線程無法訪問所以能夠不用加鎖就進行內(nèi)存分配。TLAB在eden區(qū)。青蛙客服系統(tǒng)https://download.csdn.net/download/look4liming/93326820