深入理解java虚拟机 (周志明)JVM个人总结
JIT:即時編譯器,把class中的字節(jié)碼翻譯成CPU上可以直接執(zhí)行的二進制指令。新的JIT不僅是編譯,可以分析字節(jié)碼是否可以優(yōu)化,它可以將那些經(jīng)常執(zhí)行的字節(jié)碼片段(熱點代碼)進行緩存。
java虛擬機規(guī)范 周志明
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設(shè)備的規(guī)范,它是一個虛構(gòu)出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。Java語言的一個非常重要的特點就是與平臺的無關(guān)性。而使用Java虛擬機是實現(xiàn)這一特點的關(guān)鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機后,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。Java虛擬機在執(zhí)行字節(jié)碼時,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。這就是Java的能夠一次編譯,到處運行 的原因。
Java中,字節(jié)碼是CPU構(gòu)架(JVM)的具有可移植性的機器語言
Java中,字節(jié)碼是CPU構(gòu)架(JVM)的具有可移植性的機器語言第一章 走近java
* 因為程序員把內(nèi)存控制的權(quán)力交給了java虛擬機,編碼的時候享自動內(nèi)存管理的諸多優(yōu)勢。
* 提供了一個相對安全的內(nèi)存管理和訪問機制,避免了絕大部分的內(nèi)存泄漏和指針越界問題
* 但是也是會出現(xiàn)內(nèi)存泄漏。
* Jdk進化史
* jdk 1.1 jdbc jar文件格式 jdk javabeans 語法的內(nèi)部類 反射
* 1.2 java分為三個方向 j2ee(企業(yè)) j2se(桌面開發(fā)) j2me(手機移動終端)
* collections集合 math TimerAPI
* 1.3 類庫
* 1.4 正則表達式 異常鏈 nio xml 等
* 1.5 自動裝箱 泛型 動態(tài)注解 枚舉 可變長參數(shù) 遍歷(foreach) concurrent 并發(fā)包
* 1.6 鎖 垃圾收集 類加載 算法
* 普通對象指針壓縮功能 (-XX:+ userCompressedOops)不建議開啟 jvm自動管理開啟
* 開啟壓縮指針會增加執(zhí)行代碼質(zhì)量,java 堆 指向java堆內(nèi)對象的指針都會被壓縮
* 1.7
* 1.8 lambda 表達式 map
* 第二章
* java 內(nèi)存區(qū)域與內(nèi)存溢出異常
* 方法區(qū)和堆 線程共享
* 剩下的線程隔離
* 程序計數(shù)器(program counter register)只占用了一塊比較小的內(nèi)存空間{可以忽略不計}
* 可以看作是當前線程所執(zhí)行的字節(jié)碼文件(class)的行號指示器。在虛擬機的世界中,字節(jié)碼解釋器就是通過改變計數(shù)器的值來選取下一條執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)都需要這玩意來實現(xiàn)的
* 多線程是通過線程輪流切換,并分配處理器執(zhí)行時間的方式來實現(xiàn)的。
* 1個處理器執(zhí)行一個線程 多核同時多個
* 每條線程都需要有一個獨立的程序計數(shù)器。各條線程之間計數(shù)器互不影響?yīng)毚鎯Α>€程私有的內(nèi)存。
java虛擬機棧
* 每個方法執(zhí)行都會創(chuàng)建一個棧幀,用于存放局部變量表,操作棧,動態(tài)鏈接,方法出口等。每個方法從被調(diào)用,直到被執(zhí)行完。對應(yīng)著一個棧幀在虛擬機中從入棧到出棧的過程。
* 會有兩種異常StackOverFlowError和 OutOfMemoneyError。當線程請求棧深度大于虛擬機所允許的深度就會拋出StackOverFlowError錯誤;虛擬機棧動態(tài)擴展,當擴展無法申請到足夠的內(nèi)存空間時候,拋出OutOfMemoneyError
* 本地方法棧
* 什么是Native Method
* 簡單地講,一個Native Method就是一個java調(diào)用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現(xiàn)由非java語言實現(xiàn),比如C。這個特征并非java所特有,很多其它的編程語言都有這一機制
* 與java環(huán)境外交互:
* 有時java應(yīng)用需要與java外面的環(huán)境交互。這是本地方法存在的主要原因,你可以想想java需要與一些底層系統(tǒng)如操作系統(tǒng)或某些硬件交換信息時的情況。本地方法正是這樣一種交流機制:它為我們提供了一個非常簡潔的接口,而且我們無需去了解java應(yīng)用之外的繁瑣的細節(jié)。
* 堆
* 是虛擬機中最大的一塊共享區(qū)域 在虛擬機啟動的時候創(chuàng)建 它存儲了自動內(nèi)存管理系統(tǒng) (gc垃圾收集器) 虛擬機實現(xiàn)者根據(jù)系統(tǒng)的實際需要來選擇自動內(nèi)存管理技術(shù)
* 所有類實例和數(shù)組分配內(nèi)存的區(qū)域
* 基本上采用分代收集算法
volatile變量是一種稍弱的同步機制在訪問volatile變量時不會執(zhí)行加鎖操作,因此也就不會使執(zhí)行線程阻塞,因此volatile變量是一種比synchronized關(guān)鍵字更輕量級的同步機制。讀取快 修改慢
* 1.volatile保證可見性
* 1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
* 2)禁止進行指令重排序。
* 編譯出來的只有一條字節(jié)碼指令,也不意味執(zhí)行這條指令就是一個原子操作 一條字節(jié)碼指令在解釋
* 執(zhí)行時,解釋器將要運行許多行代碼才能實現(xiàn)。
* 什么是指令重排?
* 指令重排是指JVM在編譯Java代碼的時候,或者CPU在執(zhí)行JVM字節(jié)碼的時候,對現(xiàn)有的指令順序進行重新排序。
* 指令重排的目的是為了在不改變程序執(zhí)行結(jié)果的前提下,優(yōu)化程序的運行效率。需要注意的是,這里所說的不改變執(zhí)行結(jié)果,指的是不改變單線程下的程序執(zhí)行結(jié)果。
* 一個項目一個接口,每天調(diào)用1次,1s 1s 1s 以后都會是1s嗎?
* 如何使用volatile呢
* 運算結(jié)果并不依賴變量的當前值,后者能夠確保只有單一的線程修改變量的值
準備準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值得階段,這些變量所使用的內(nèi)存都講在方法區(qū)中進行分配。這時候進行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
* 在準備階段,為類變量(static修飾)在方法區(qū)中分配內(nèi)存并設(shè)置初始值。
* private static int var = 100;
* 準備階段完成后,var 值為0,而不是100。在初始化階段,才會把100賦值給val,但是有個特殊情況:
* private static final int VAL= 100;
* 在編譯階段會為VAL生成ConstantValue屬性,在準備階段虛擬機會根據(jù)ConstantValue屬性將VAL賦值為100。
* 初始化
* 初始化階段是執(zhí)行類構(gòu)造器方法的過程,方法由類變量的賦值動作和靜態(tài)語句塊按照在源文件出現(xiàn)的順序合并而成,該合并操作由編譯器完成。
* 開始執(zhí)行java代碼(或者說字節(jié)碼)
查看JVM使用的默認的垃圾收集器
查看步驟
cmd執(zhí)行命令:
java -XX:+PrintCommandLineFlags -version
jdk1.7 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默認垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默認垃圾收集器G1
上邊新生代 下邊老年代
標記-清除算法
復(fù)制算法
標記-整理算法
新生代 單線程
ParNew
新生代多線程
Parallel ScaVenge
新生代 復(fù)制算法 可控制的吞吐量 吞吐量優(yōu)先
Serial old
老年代 單線程標記-整理算法
Parallel Old 多線程 標記-整理算法
cms
最短停頓時間為目標(快)
標記-清除 再整理
Serial old是cms的后備方案
算法過程:
1.S0與S1的區(qū)間明顯較小,有效新生代空間為Eden+S0/S1,因此有效空間就大,增加了內(nèi)存使用率
2.有利于對象代的計算,當一個對象在S0/S1中達到設(shè)置的XX:MaxTenuringThreshold值后,會將其分到老年代中,設(shè)想一下,如果沒有S0/S1,直接分成兩個區(qū),該如何計算對象經(jīng)過了多少次GC還沒被釋放,你可能會說,在對象里加一個計數(shù)器記錄經(jīng)過的GC次數(shù),或者存在一張映射表記錄對象和GC次數(shù)的關(guān)系,是的,可以,但是這樣的話,會掃描整個新生代中的對象, 有了S0/S1我們就可以只掃描S0/S1區(qū)了
在年輕代中經(jīng)歷了N次(可配置)垃圾回收后仍然存活的對象,就會被復(fù)制到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。
針對年老代的垃圾回收即Full GC。
Jvm查看
對象的內(nèi)布局
對象頭 實例數(shù)據(jù) 對象填充
對象頭 mark word 存儲 64位或者32位指針
25 哈希嗎
4 分代年齡
2 鎖標志
輕量級 重量級 gc標志 可偏向
top
ps -ef | grep java
jps 條件 pid -q –m –l -v
jstat 條件 pid
-class -gc –gccapacity –gcutil –gccause -gcnew
-gcnewcapacity –gcold –gcoldcapacity -gcpermcapacity
-compiler -printcompilation
jmap 條件 pid
-dump –finalizerinfo –heap –histo –permstat -F
jvm dump 分析
mat 英文 memory analyzer tool
jvm dump 分析工具 (MAT)
Memory Analyzer tool
Histogram 所有實例的分配情況
Dominator Tree 堆的最大對象
Leak Suspecks 列出懷疑的內(nèi)存泄漏處
-gc (jstat -gc pid 1000 5 )
S0C: Survivor0(幸存區(qū)0)大小(KB)
S1C: Survivor1(幸存區(qū)1)1大小(KB)
S0U: Survivor0(幸存區(qū)0)已使用大小(KB)
S1U: Survivor1(幸存區(qū)1)已使用大小(KB)
EC : Eden(伊甸區(qū))大小(KB)
EU : Eden(伊甸區(qū))已使用大小(KB)
OC :老年代大小(KB)
OU : 老年代已使用大小(KB)
PC : Perm永久代大小(KB)
PU : Perm永久代已使用大小(KB)
YGC:新生代GC個數(shù)
YGCT:新生代GC的耗時(秒)
FGC :Full GC次數(shù)
FGCT:Full GC耗時(秒)
GCT :GC總耗時(秒)
常用 jvm tomcat 配置參數(shù)
-XX:+UseSerialGC:在新生代和老年代使用串行回收器。
-XX:+SuivivorRatio:設(shè)置 eden 區(qū)大小和 survivor 區(qū)大小的比例。
-XX:+PretenureSize :設(shè)置大對象直接進入老年代的閾值。當對象的大小超過這個值時,將直接在老年代分配 默認15。
-XX:MaxTenuringThreshold:設(shè)置對象進入老年代的年齡的最大值。每一次 Minor GC 后,對象年齡就加 1。任何大于這個年齡的對象,一定會進入老年代。
-XX:+UseParNewGC: 在新生代使用并行收集器。
-XX:+UseParallelOldGC: 老年代使用并行回收收集器。
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)。通常情況下可以和 CPU 數(shù)量相等。但在 CPU 數(shù)量比較多的情況下,設(shè)置相對較小的數(shù)值也是合理的。
-XX:MaxGCPauseMills:設(shè)置最大垃圾收集停頓時間。它的值是一個大于 0 的整數(shù)。收集器在工作時,會調(diào)整 Java 堆大小或者其他一些參數(shù),盡可能地把停頓時間控制在 MaxGCPauseMills 以內(nèi)。
-XX:GCTimeRatio:設(shè)置吞吐量大小,它的值是一個 0-100 之間的整數(shù)。假設(shè) GCTimeRatio 的值為 n,那么系統(tǒng)將花費不超過 1/(1+n) 的時間用于垃圾收集。
-XX:+UseAdaptiveSizePolicy:打開自適應(yīng) GC 策略。在這種模式下,新生代的大小,eden 和 survivor 的比例、晉升老年代的對象年齡等參數(shù)會被自動調(diào)整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。
-XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。
-XX:+ParallelCMSThreads: 設(shè)定 CMS 的線程數(shù)量。
滴滴面試問到的 68 是什么的比例
XX:+CMSInitiatingOccupancyFraction:設(shè)置 CMS 收集器在老年代空間被使用多少后觸發(fā),默認為 68%。
XX:+UseFullGCsBeforeCompaction:設(shè)定進行多少次 CMS 垃圾回收后,進行一次內(nèi)存壓縮。
-XX:+CMSClassUnloadingEnabled:允許對類元數(shù)據(jù)進行回收。
-XX:+CMSParallelRemarkEndable:啟用并行重標記。
-XX:CMSInitatingPermOccupancyFraction:當永久區(qū)占用率達到這一百分比后,啟動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
-XX:UseCMSInitatingOccupancyOnly:表示只在到達閾值的時候,才進行 CMS 回收。
-XX:+CMSIncrementalMode:使用增量模式,比較適合單 CPU。
4. 與 G1 回收器相關(guān)的參數(shù)
-XX:+UseG1GC:使用 G1 回收器。
-XX:+UnlockExperimentalVMOptions:允許使用實驗性參數(shù)。
-XX:+MaxGCPauseMills:設(shè)置最大垃圾收集停頓時間。
-XX:+GCPauseIntervalMills:設(shè)置停頓間隔時間。
1.手動調(diào)整
Xmn
-Xms
-XX:NewRatio=N
手動指定堆內(nèi)存大小和代空間比例,一般要多次試驗
2.自動參數(shù)調(diào)整
XX:MaxGCPauseMillis=N 可接受最大停頓時間
-XX:GCTimeRatio=N 可接受GC時間占比(目標吞吐量) 吞吐量=1-1/(1+N)
步驟:
1.MaxGCPauseMillis優(yōu)先級高,JVM會自動調(diào)整堆大小和代空間值,以期滿足MaxGCPauseMillis
2.當MaxGCPauseMillis滿足后,JVM會增大堆大小,直到滿足GCTimeRatio
3.當MaxGCPauseMillis和GCTimeRadio都滿足后,JVM會盡可能以最小堆大小來實現(xiàn)這兩個指標參數(shù)
Full GC原因:
并發(fā)模式失效:新生代發(fā)生GC時,老年代沒有足夠內(nèi)存容納晉升對象
晉升失敗:老年代雖然有足夠容納晉升對象的內(nèi)存,但內(nèi)存都是碎片,導(dǎo)致晉升失敗
*參數(shù)調(diào)整:避免并發(fā)模式失效和晉升失敗
-XX:+UseCMSInitiatingOccupancyOnly 根據(jù)Old內(nèi)存使用閾值決定何時CMS, 默認是false,會用更復(fù)雜的算法決定何時CMS
-XX:CMSInitingOccupancyFraction=N default N=70,老年代內(nèi)存使用70%時就發(fā)生CMS
N設(shè)置太大,容易并發(fā)模式失效;N太小,CMS過于頻繁,而CMS也會導(dǎo)致stop-the-world
-XX:ConGCThreads=N GC的線程會100%占用CPU,如果發(fā)生并發(fā)模式失敗,而N還小于CPU核心數(shù),此時可以增加N。
如果沒有發(fā)生并發(fā)模式失敗,此時可以減少N,以讓應(yīng)用程序有更多CPU執(zhí)行
Perm持久代GC調(diào)優(yōu)
持久代內(nèi)存滿了會引發(fā)Full GC
持久代GC調(diào)優(yōu)主要是讓持久代也進行CMS收集
-XX:+CMSPermGenSweepingEnable 使持久代使用CMS收集器
-XX:+CMSClassUnloadingEnable 使持久代能真正釋放不再被使用的類。默認是不會釋放類的元數(shù)據(jù)的
增量式CMS:普通CMS線程會占用100%的cpu負載,增量式CMS會讓出一定CPU負載給應(yīng)用線程
這適合在單核CPU使用,顯然已經(jīng)沒啥用處了
老年代(Old Generation)
老年代(Old Generation)老年代的GC實現(xiàn)要復(fù)雜得多。老年代內(nèi)存空間通常會更大,里面的對象是垃圾的概率也更小。
老年代GC發(fā)生的頻率比年輕代小很多。同時, 因為預(yù)期老年代中的對象大部分是存活的, 所以不再使用標記和復(fù)制(Mark and Copy)算法。而是采用移動對象的方式來實現(xiàn)最小化內(nèi)存碎片。老年代空間的清理算法通常是建立在不同的基礎(chǔ)上的。原則上,會執(zhí)行以下這些步驟:
通過標志位(marked bit),標記所有通過 GC roots 可達的對象.
刪除所有不可達對象
整理老年代空間中的內(nèi)容,方法是將所有的存活對象復(fù)制,從老年代空間開始的地方,依次存放。
Minor GC vs Major GC vs Full GC
垃圾收集事件(Garbage Collection events)通常分為: 小型GC(Minor GC) - 大型GC(Major GC) - 和完全GC(Full GC) 。
小型GC(Minor GC)
年輕代內(nèi)存的垃圾收集事件稱為小型GC。這個定義既清晰又得到廣泛共識。對于小型GC事件,有一些有趣的事情你應(yīng)該了解一下:
1. 當JVM無法為新對象分配內(nèi)存空間時總會觸發(fā) Minor GC,比如 Eden 區(qū)占滿時。所以(新對象)分配頻率越高, Minor GC 的頻率就越高。
2. Minor GC 事件實際上忽略了老年代。從老年代指向年輕代的引用都被認為是GC Root。而從年輕代指向老年代的引用在標記階段全部被忽略。
3. 與一般的認識相反, Minor GC 每次都會引起全線停頓(stop-the-world ), 暫停所有的應(yīng)用線程。對大多數(shù)程序而言,暫停時長基本上是可以忽略不計的, 因為 Eden 區(qū)的對象基本上都是垃圾, 也不怎么復(fù)制到存活區(qū)/老年代。如果情況不是這樣, 大部分新創(chuàng)建的對象不能被垃圾回收清理掉, 則 Minor GC的停頓就會持續(xù)更長的時間。
所以 Minor GC 的定義很簡單 —— Minor GC 清理的就是年輕代。
Major GC vs Full GC
沒有明確的定義
Major GC(大型GC) 清理的是老年代空間(Old space)。
Full GC(完全GC)清理的是整個堆, 包括年輕代和老年代空間。
很多 Major GC 是由 Minor GC 觸發(fā)的, 所以很多情況下這兩者是不可分離的。另一方面, 像G1這樣的垃圾收集算法執(zhí)行的是部分區(qū)域垃圾回收 回收區(qū)分也不是很明確
這也讓我們認識到,不應(yīng)該去操心是叫 Major GC 呢還是叫 Full GC, 我們應(yīng)該關(guān)注的是: 某次GC事件 是否停止所有線程,或者是與其他線程并發(fā)執(zhí)行。
一個線程OOM,進程里其他線程還能運行么?
正常linux 項目啟動 oom error 就會造成項目停止
這問題正常也是回答 其他線程也會停止,總感覺oom 出問題都會停止
但是這個線程有時候真是特殊,一個線程oom并不一定其他的線程也停止。
既然一個線程oom,那它就會觸發(fā)gc,gc回收后如果有足夠的空間,并不會造成其他的線程停止。
https://cloud.tencent.com/developer/article/1614156
https://blog.csdn.net/qq_40298351/article/details/121256296
總結(jié)
以上是生活随笔為你收集整理的深入理解java虚拟机 (周志明)JVM个人总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【读书笔记《凤凰架构》- 构架可靠的大型
- 下一篇: 周志明论架构之道:从SOA时代到微服务时