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