JVM面試題(史上最強、持續更新、吐血推薦)


瘋狂創客圈 史上最強 面試題 系列
Java基礎
JVM面試題(史上最強、持續更新、吐血推薦) https://www.cnblogs.com/crazymakercircle/p/14365820.html
Java基礎面試題(史上最全、持續更新、吐血推薦) https://www.cnblogs.com/crazymakercircle/p/14366081.html
死鎖面試題(史上最強、持續更新) http://www.javashuo.com/article/p-uyudvdol-vd.html
多線程、線程池、內置鎖 面試題 (史上最強、持續更新) http://www.javashuo.com/article/p-tecubweb-vd.html
JUC併發包與容器類 - 面試題(史上最強、持續更新) http://www.javashuo.com/article/p-xcyjkbgi-vd.html
SpringBoot - 面試題(史上最強、持續更新) http://www.javashuo.com/article/p-xjwdvxjl-vd.html
Linux面試題(史上最全、持續更新、吐血推薦) https://www.cnblogs.com/crazymakercircle/p/14366893.html
分佈式、高併發、設計模式、架構
Zookeeper 面試題(史上最強、持續更新) http://www.javashuo.com/article/p-fvxlillp-vd.html
Mysql 面試題(史上最強、持續更新) http://www.javashuo.com/article/p-xhmrgfwk-vd.html
Redis 面試題 - 收藏版(史上最強、持續更新) http://www.javashuo.com/article/p-vkywkhov-vd.html
SpringCloud 面試題 - 收藏版(史上最強、持續更新) http://www.javashuo.com/article/p-pptrkvmu-vd.html
Netty 面試題 (史上最強、持續更新) http://www.javashuo.com/article/p-guuqkocg-vd.html
消息隊列、RabbitMQ、Kafka、RocketMQ面試題 (史上最全、持續更新、吐血推薦) https://www.cnblogs.com/crazymakercircle/p/14367425.html
設計模式面試題 (史上最全、持續更新、吐血推薦) https://www.cnblogs.com/crazymakercircle/p/14367101.html

Java內存區域

解釋 Java 堆空間及 GC?

當經過 Java 命令啓動 Java 進程的時候,會爲它分配內存。內存的一部分用於建立堆空間,當程序中建立對象的時候,就從對空間中分配內存。GC 是 JVM 內部的一個進程,回收無效對象的內存用於未來的分配。html

說一下 JVM 的主要組成部分及其做用?

img

JVM包含兩個子系統和兩個組件,兩個子系統爲Class loader(類裝載)、Execution engine(執行引擎);兩個組件爲Runtime data area(運行時數據區)、Native Interface(本地接口)。java

  • Class loader(類裝載):根據給定的全限定名類名(如:java.lang.Object)來裝載class文件到Runtime data area中的method area。
  • Execution engine(執行引擎):執行classes中的指令。
  • Native Interface(本地接口):與native libraries交互,是其它編程語言交互的接口。
  • Runtime data area(運行時數據區域):這就是咱們常說的JVM的內存。

做用 :首先經過編譯器把 Java 代碼轉換成字節碼,類加載器(ClassLoader)再把字節碼加載到內存中,將其放在運行時數據區(Runtime data area)的方法區內,而字節碼文件只是 JVM 的一套指令集規範,並不能直接交給底層操做系統去執行,所以須要特定的命令解析器執行引擎(Execution Engine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程當中須要調用其餘語言的本地庫接口(Native Interface)來實現整個程序的功能。程序員

下面是Java程序運行機制詳細說明面試

Java程序運行機制步驟算法

  • 首先利用IDE集成開發工具編寫Java源代碼,源文件的後綴爲.java;
  • 再利用編譯器(javac命令)將源代碼編譯成字節碼文件,字節碼文件的後綴名爲.class;
  • 運行字節碼的工做是由解釋器(java命令)來完成的。

在這裏插入圖片描述

從上圖能夠看,java文件經過編譯器變成了.class文件,接下來類加載器又將這些.class文件加載到JVM中。
其實能夠一句話來解釋:類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個 java.lang.Class對象,用來封裝類在方法區內的數據結構。sql

說一下 JVM 運行時數據區? 或者:說一下JVM內存模型?

思路: 給面試官畫一下JVM內存模型圖,並描述每一個模塊的定義,做用,以及可能會存在的問題,如棧溢出等。編程

Java 虛擬機在執行 Java 程序的過程當中會把它所管理的內存區域劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時間,有些區域隨着虛擬機進程的啓動而存在,有些區域則是依賴線程的啓動和結束而創建和銷燬。Java 虛擬機所管理的內存被劃分爲以下幾個區域:設計模式

img

  • 程序計數器(Program Counter Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工做是經過改變這個計數器的值,來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都須要依賴這個計數器來完成;
  • Java 虛擬機棧(Java Virtual Machine Stacks):用於存儲局部變量表、操做數棧、動態連接、方法出口等信息;
  • 本地方法棧(Native Method Stack):與虛擬機棧的做用是同樣的,只不過虛擬機棧是服務 Java 方法的,而本地方法棧是爲虛擬機調用 Native 方法服務的;
  • Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被全部線程共享的,幾乎全部的對象實例都在這裏分配內存;
  • 方法區(Methed Area):用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。

深拷貝和淺拷貝

淺拷貝(shallowCopy)只是增長了一個指針指向已存在的內存地址,數組

深拷貝(deepCopy)是增長了一個指針而且申請了一個新的內存,使這個增長的指針指向這個新的內存,瀏覽器

使用深拷貝的狀況下,釋放內存的時候不會由於出現淺拷貝時釋放同一個內存的錯誤。

淺複製:僅僅是指向被複制的內存地址,若是原地址發生改變,那麼淺複製出來的對象也會相應的改變。

深複製:在計算機中開闢一塊新的內存地址用於存放複製的對象。

說一下堆棧的區別?

物理地址

堆的物理地址分配對對象是不連續的。所以性能慢些。在GC的時候也要考慮到不連續的分配,因此有各類算法。好比,標記-消除,複製,標記-壓縮,分代(即新生代使用複製算法,老年代使用標記——壓縮)

棧使用的是數據結構中的棧,先進後出的原則,物理地址分配是連續的。因此性能快。

內存分別

堆由於是不連續的,因此分配的內存是在運行期確認的,所以大小不固定。通常堆大小遠遠大於棧。

棧是連續的,因此分配的內存大小要在編譯期就確認,大小是固定的。

存放的內容

堆存放的是對象的實例和數組。所以該區更關注的是數據的存儲

棧存放:局部變量,操做數棧,返回結果。該區更關注的是程序方法的執行。

PS:

  1. 靜態變量放在方法區
  2. 靜態的對象仍是放在堆。

程序的可見度

堆對於整個應用程序都是共享、可見的。

棧只對於線程是可見的。因此也是線程私有。他的生命週期和線程相同。

Java 中堆和棧有什麼區別?

JVM 中堆和棧屬於不一樣的內存區域,使用目的也不一樣。棧經常使用於保存方法幀和局部變量,而對象老是在堆上分配。棧一般都比堆小,也不會在多個線程之間共享,而堆被整個 JVM 的全部線程共享。

隊列和棧是什麼?有什麼區別?

隊列和棧都是被用來預存儲數據的。

  • 操做的名稱不一樣。隊列的插入稱爲入隊,隊列的刪除稱爲出隊。棧的插入稱爲進棧,棧的刪除稱爲出棧。
  • 可操做的方式不一樣。隊列是在隊尾入隊,隊頭出隊,即兩邊均可操做。而棧的進棧和出棧都是在棧頂進行的,沒法對棧底直接進行操做。
  • 操做的方法不一樣。隊列是先進先出(FIFO),即隊列的修改是依先進先出的原則進行的。新來的成員老是加入隊尾(不能從中間插入),每次離開的成員老是隊列頭上(不容許中途離隊)。而棧爲後進先出(LIFO),即每次刪除(出棧)的老是當前棧中最新的元素,即最後插入(進棧)的元素,而最早插入的被放在棧的底部,要到最後才能刪除。

虛擬機棧(線程私有)

是描述java方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。 每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態連接(Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。 棧幀隨着方法調用而建立,隨着方法結束而銷燬——不管方法是正常完成仍是異常完成(拋出了在方法內未被捕獲的異常)都算做方法結束。
在這裏插入圖片描述

程序計數器(線程私有)

一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的程序計數器,這類內存也稱爲「線程私有」 的內存。
正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址) 。若是仍是 Native 方法,則爲空。這個內存區域是惟一一個在虛擬機中沒有規定任何 OutOfMemoryError 狀況的區域。

什麼是直接內存?

直接內存並非 JVM 運行時數據區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提供了基於 Channel 與 Buffer 的 IO 方式, 它可使用 Native 函數庫直接分配堆外內存, 而後使用DirectByteBuffer 對象做爲這塊內存的引用進行操做(詳見: Java I/O 擴展), 這樣就避免了在 Java堆和 Native 堆中來回複製數據, 所以在一些場景中能夠顯著提升性能。
在這裏插入圖片描述

HotSpot虛擬機對象探祕

對象的建立

說到對象的建立,首先讓咱們看看 Java 中提供的幾種對象建立方式:

Header 解釋
使用new關鍵字 調用了構造函數
使用Class的newInstance方法 調用了構造函數
使用Constructor類的newInstance方法 調用了構造函數
使用clone方法 沒有調用構造函數
使用反序列化 沒有調用構造函數

下面是對象建立的主要流程:

img

虛擬機遇到一條new指令時,先檢查常量池是否已經加載相應的類,若是沒有,必須先執行相應的類加載。類加載經過後,接下來分配內存。若Java堆中內存是絕對規整的,使用「指針碰撞「方式分配內存;若是不是規整的,就從空閒列表中分配,叫作」空閒列表「方式。劃份內存時還須要考慮一個問題-併發,也有兩種方式: CAS同步處理,或者本地線程分配緩衝(Thread Local Allocation Buffer, TLAB)。而後內存空間初始化操做,接着是作一些必要的對象設置(元信息、哈希碼…),最後執行<init>方法。

爲對象分配內存

類加載完成後,接着會在Java堆中劃分一塊內存分配給對象。內存分配根據Java堆是否規整,有兩種方式:

  • 指針碰撞:若是Java堆的內存是規整,即全部用過的內存放在一邊,而空閒的的放在另外一邊。分配內存時將位於中間的指針指示器向空閒的內存移動一段與對象大小相等的距離,這樣便完成分配內存工做。
  • 空閒列表:若是Java堆的內存不是規整的,則須要由虛擬機維護一個列表來記錄那些內存是可用的,這樣在分配的時候能夠從列表中查詢到足夠大的內存分配給對象,並在分配後更新列表記錄。

選擇哪一種分配方式是由 Java 堆是否規整來決定的,而 Java 堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。

內存分配的兩種方式

處理併發安全問題

對象的建立在虛擬機中是一個很是頻繁的行爲,哪怕只是修改一個指針所指向的位置,在併發狀況下也是不安全的,可能出現正在給對象 A 分配內存,指針還沒來得及修改,對象 B 又同時使用了原來的指針來分配內存的狀況。解決這個問題有兩種方案:

  • 對分配內存空間的動做進行同步處理(採用 CAS + 失敗重試來保障更新操做的原子性);
  • 把內存分配的動做按照線程劃分在不一樣的空間之中進行,即每一個線程在 Java 堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer, TLAB)。哪一個線程要分配內存,就在哪一個線程的 TLAB 上分配。只有 TLAB 用完並分配新的 TLAB 時,才須要同步鎖。經過-XX:+/-UserTLAB參數來設定虛擬機是否使用TLAB。

內存分配時保證線程安全的兩種方式

對象的訪問定位

Java程序須要經過 JVM 棧上的引用訪問堆中的具體對象。對象的訪問方式取決於 JVM 虛擬機的實現。目前主流的訪問方式有 句柄直接指針 兩種方式。

指針: 指向對象,表明一個對象在內存中的起始地址。

句柄: 能夠理解爲指向指針的指針,維護着對象的指針。句柄不直接指向對象,而是指向對象的指針(句柄不發生變化,指向固定內存地址),再由對象的指針指向對象的真實內存地址。

句柄訪問

Java堆中劃分出一塊內存來做爲句柄池,引用中存儲對象的句柄地址,而句柄中包含了對象實例數據對象類型數據各自的具體地址信息,具體構造以下圖所示:

img

優點:引用中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中實例數據指針,而引用自己不須要修改。

直接指針

若是使用直接指針訪問,引用 中存儲的直接就是對象地址,那麼Java堆對象內部的佈局中就必須考慮如何放置訪問類型數據的相關信息。

img

優點:速度更,節省了一次指針定位的時間開銷。因爲對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是很是可觀的執行成本。HotSpot 中採用的就是這種方式。

64 位 JVM 中,int 的長度是多數?

Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的 Java 虛擬機中,int 類型的長度是相同的。

32 位和 64 位的 JVM,int 類型變量的長度是多數?

32 位和 64 位的 JVM 中,int 類型變量的長度是相同的,都是 32 位或者 4個字節。

怎樣經過 Java 程序來判斷 JVM 是 32 位 仍是 64位?

你能夠檢查某些系統屬性如 sun.arch.data.model 或 os.arch 來獲取該信息。

32 位 JVM 和 64 位 JVM 的最大堆內存分別是多數?

理論上說上 32 位的 JVM 堆內存能夠到達 2^32, 即 4GB,但實際上會比這個小不少。不一樣操做系統之間不一樣,如 Windows 系統大約 1.5GB,Solaris 大約3GB。64 位 JVM 容許指定最大的堆內存,理論上能夠達到 2^64,這是一個很是大的數字,實際上你能夠指定堆內存大小到 100GB。甚至有的 JVM,如 Azul,堆內存到 1000G 都是可能的。

JRE、JDK、JVM 及 JIT 之間有什麼不一樣?

JRE 表明 Java 運行時(Java run-time),是運行 Java 引用所必須的。

JDK 表明 Java 開發工具(Java development kit),是 Java 程序的開發工具,如 Java編譯器,它也包含 JRE。

JVM 表明 Java 虛擬機(Java virtual machine),它的責任是運行 Java 應用。

JIT 表明即時編譯(Just In Time compilation),當代碼執行的次數超過必定的閾值時,會將 Java 字節碼轉換爲本地代碼,如,主要的熱點代碼會被準換爲本地代碼,這樣有利大幅度提升 Java 應用的性能。

內存溢出異常

Java會存在內存泄漏嗎?請簡單描述

內存泄漏是指再也不被使用的對象或者變量一直被佔據在內存中。理論上來講,Java是有GC垃圾回收機制的,也就是說,再也不被使用的對象,會被GC自動回收掉,自動從內存中清除。

可是,即便這樣,Java也仍是存在着內存泄漏的狀況,java致使內存泄露的緣由很明確:長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄露,儘管短生命週期對象已經再也不須要,可是由於長生命週期對象持有它的引用而致使不能被回收,這就是java中內存泄露的發生場景。

什麼狀況下會發生棧內存溢出。

思路: 描述棧定義,再描述爲何會溢出,再說明一下相關配置參數,OK的話能夠給面試官手寫是一個棧溢出的demo。

參考答案:

  • 棧是線程私有的,他的生命週期與線程相同,每一個方法在執行的時候都會建立一個棧幀,用來存儲局部變量表,操做數棧,動態連接,方法出口等信息。局部變量表又包含基本數據類型,對象引用類型
  • 若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError異常,方法遞歸調用產生這種結果。
  • 若是Java虛擬機棧能夠動態擴展,而且擴展的動做已經嘗試過,可是沒法申請到足夠的內存去完成擴展,或者在新創建線程的時候沒有足夠的內存去建立對應的虛擬機棧,那麼Java虛擬機將拋出一個OutOfMemory 異常。(線程啓動過多)
  • 參數 -Xss 去調整JVM棧的大小

垃圾收集器

簡述Java垃圾回收機制

在java中,程序員是不須要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常狀況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

GC是什麼?爲何要GC

GC 是垃圾收集的意思(Gabage Collection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存

回收會致使程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能能夠自動監測對象是否超過做用域從而達到自動

回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操做方法。

垃圾回收的優勢和原理。並考慮2種回收機制

java語言最顯著的特色就是引入了垃圾回收機制,它使java程序員在編寫程序時再也不考慮內存管理的問題。

因爲有這個垃圾回收機制,java中的對象再也不有「做用域」的概念,只有引用的對象纔有「做用域」。

垃圾回收機制有效的防止了內存泄露,能夠有效的使用可以使用的內存。

垃圾回收器一般做爲一個單獨的低級別的線程運行,在不可預知的狀況下對內存堆中已經死亡的或很長時間沒有用過的對象進行清除和回收。

程序員不能實時的對某個對象或全部對象調用垃圾回收器進行垃圾回收。

垃圾回收有分代複製垃圾回收、標記垃圾回收、增量垃圾回收。

垃圾回收器的基本原理是什麼?垃圾回收器能夠立刻回收內存嗎?有什麼辦法主動通知虛擬機進行垃圾回收?

對於GC來講,當程序員建立對象時,GC就開始監控這個對象的地址、大小以及使用狀況。

一般,GC採用有向圖的方式記錄和管理堆(heap)中的全部對象。經過這種方式肯定哪些對象是"可達的",哪些對象是"不可達的"。當GC肯定一些對象爲"不可達"時,GC就有責任回收這些內存空間。

能夠。程序員能夠手動執行System.gc(),通知GC運行,可是Java語言規範並不保證GC必定會執行。

你能保證 GC 執行嗎?

不能,雖然你能夠調用 System.gc() 或者 Runtime.gc(),可是沒有辦法保證 GC的執行。

Java 中都有哪些引用類型?

  • 強引用:發生 gc 的時候不會被回收。
  • 軟引用:有用但不是必須的對象,在發生內存溢出以前會被回收。
  • 弱引用:有用但不是必須的對象,在下一次GC時會被回收。
  • 虛引用(幽靈引用/幻影引用):沒法經過虛引用得到對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。

介紹一下強引用、軟引用、弱引用、虛引用的區別?

思路: 先說一下四種引用的定義,能夠結合代碼講一下,也能夠擴展談到ThreadLocalMap裏弱引用用處。

參考答案:

1)強引用

咱們平時new了一個對象就是強引用,例如 Object obj = new Object();即便在內存不足的狀況下,JVM寧願拋出OutOfMemory錯誤也不會回收這種對象。

2)軟引用

若是一個對象只具備軟引用,則內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。

SoftReference<String> softRef=new SoftReference<String>(str);     // 軟引用

用處: 軟引用在實際中有重要的應用,例如瀏覽器的後退按鈕。按後退時,這個後退時顯示的網頁內容是從新進行請求仍是從緩存中取出呢?這就要看具體的實現策略了。

(1)若是一個網頁在瀏覽結束時就進行內容的回收,則按後退查看前面瀏覽過的頁面時,須要從新構建

(2)若是將瀏覽過的網頁存儲到內存中會形成內存的大量浪費,甚至會形成內存溢出

以下代碼:

Browser prev = new Browser();               // 獲取頁面進行瀏覽
SoftReference sr = new SoftReference(prev); // 瀏覽完畢後置爲軟引用        
if(sr.get()!=null){ 
    rev = (Browser) sr.get();           // 尚未被回收器回收,直接獲取
}else{
    prev = new Browser();               // 因爲內存吃緊,因此對軟引用的對象回收了
    sr = new SoftReference(prev);       // 從新構建
}

3)弱引用

具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。

String str=new String("abc");    
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str=null;
等價於
str = null;
System.gc();

4)虛引用

若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

怎麼判斷對象是否能夠被回收?

垃圾收集器在作垃圾回收的時候,首先須要斷定的就是哪些內存是須要被回收的,哪些對象是「存活」的,是不能夠被回收的;哪些對象已經「死掉」了,須要被回收。

通常有兩種方法來判斷:

  • 引用計數器法:爲每一個對象建立一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器爲 0 時就能夠被回收。它有一個缺點不能解決循環引用的問題;
  • 可達性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證實此對象是能夠被回收的。

在Java中,對象何時能夠被垃圾回收

當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就能夠被回收了。
垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。

JVM 運行時堆內存如何分代?

Java 堆從 GC 的角度還能夠細分爲: 新生代(Eden 區、 From Survivor 區和 To Survivor 區)和老年代。

參考圖1:

在這裏插入圖片描述

參考圖2:

在這裏插入圖片描述

從圖中能夠看出: 堆大小 = 新生代 + 老年代。其中,堆的大小能夠經過參數 –Xms、-Xmx 來指定。

默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值爲 1:2 ( 該值能夠經過參數 –XX:NewRatio 來指定 ),

即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小

其中,新生代 ( Young ) 被細分爲 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名爲 from 和 to,以示區分

默認的,Eden: from : to = 8 :1 : 1 ( 能夠經過參數–XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小

JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來爲對象服務,因此不管何時,老是有一塊Survivor區域是空閒着的

所以,新生代實際可用的內存空間爲 9/10 ( 即90% )的新生代空間。

新生代

是用來存放新生的對象。通常佔據堆的 1/3 空間。因爲頻繁建立對象,因此新生代會頻繁觸發MinorGC 進行垃圾回收。新生代又分爲 Eden區、 ServivorFrom、 ServivorTo 三個區。
Eden 區
Java 新對象的出生地(若是新建立的對象佔用內存很大,則直接分配到老年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行一次垃圾回收。
Servivor from 區
上一次 GC 的倖存者,做爲這一次 GC 的被掃描者。
Servivor to 區
保留了一次 MinorGC 過程當中的倖存者。
MinorGC 的過程(複製->清空->互換)
MinorGC 採用複製算法。

  1. eden、 servicorFrom 複製到 ServicorTo,年齡+1
    首先,把 Eden 和 ServivorFrom 區域中存活的對象複製到 ServicorTo 區域(若是有對象的年齡以及達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1(若是 ServicorTo 不夠位置了就放到老年區);
  2. 清空 eden、 servicorFrom
    而後,清空 Eden 和 ServicorFrom 中的對象;
  3. ServicorTo 和 ServicorFrom 互換
    最後, ServicorTo 和 ServicorFrom 互換,原 ServicorTo 成爲下一次 GC 時的 ServicorFrom區。

老年代

主要存放應用程序中生命週期長的內存對象。
老年代的對象比較穩定,因此 MajorGC (經常稱之爲 FULL GC)不會頻繁執行。在進行 FULL GC前通常都先進行了一次 MinorGC,使得有新生代的對象晉身入老年代,致使空間不夠用時才觸發。當沒法找到足夠大的連續空間分配給新建立的較大對象時也會提早觸發一次 MajorGC 進行垃圾回收騰出空間。
FULL GC 採用標記清除算法:首先掃描一次全部老年代,標記出存活的對象,而後回收沒有標記的對象。 ajorGC 的耗時比較長,由於要掃描再回收。 FULL GC 會產生內存碎片,爲了減小內存損耗,咱們通常須要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的時候,就會拋出 OOM(Out of Memory)異常。

永久代

指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被放入永久區域, 它和和存放實例的區域不一樣,GC 不會在主程序運行期對永久區域進行清理。因此這也致使了永久代的區域會隨着加載的 Class 的增多而脹滿,最終拋出 OOM 異常。

JVM內存爲何要分紅新生代,老年代,持久代。新生代中爲何要分爲Eden和Survivor。

思路: 先講一下JAVA堆,新生代的劃分,再談談它們之間的轉化,相互之間一些參數的配置(如: –XX:NewRatio,–XX:SurvivorRatio等),再解釋爲何要這樣劃分,最好加一點本身的理解。

參考答案:

這樣劃分的目的是爲了使 JVM 可以更好的管理堆內存中的對象,包括內存的分配以及回收。

1)共享內存區劃分

  • 共享內存區 = 持久帶 + 堆
  • 持久帶 = 方法區 + 其餘
  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1

2)一些參數的配置

  • 默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值爲 1:2 ,能夠經過參數 –XX:NewRatio 配置。
  • 默認的,Eden : from : to = 8 : 1 : 1 ( 能夠經過參數 –XX:SurvivorRatio 來設定)
  • Survivor區中的對象被複制次數爲15(對應虛擬機參數 -XX:+MaxTenuringThreshold)

3)爲何要分爲Eden和Survivor?爲何要設置兩個Survivor區?

  • 若是沒有Survivor,Eden區每進行一次Minor GC,存活的對象就會被送到老年代。老年代很快被填滿,觸發Major GC.老年代的內存空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多,因此須要分爲Eden和Survivor。
  • Survivor的存在乎義,就是減小被送到老年代的對象,進而減小Full GC的發生,Survivor的預篩選保證,只有經歷16次Minor GC還能在新生代中存活的對象,纔會被送到老年代。
  • 設置兩個Survivor區最大的好處就是解決了碎片化,剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活對象又會被複制送入第二塊survivor space S1(這個過程很是重要,由於這種複製算法保證了S1中來自S0和Eden兩部分的存活對象佔用連續的內存空間,避免了碎片化的發生)

JVM中一次完整的GC流程是怎樣的,對象如何晉升到老年代

思路: 先描述一下Java堆內存劃分,再解釋Minor GC,Major GC,full GC,描述它們之間轉化流程。

個人答案:

  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1
  • 當 Eden 區的空間滿了, Java虛擬機會觸發一次 Minor GC,以收集新生代的垃圾,存活下來的對象,則會轉移到 Survivor區。
  • 大對象(須要大量連續內存空間的Java對象,如那種很長的字符串)直接進入老年態
  • 若是對象在Eden出生,並通過第一次Minor GC後仍然存活,而且被Survivor容納的話,年齡設爲1,每熬過一次Minor GC,年齡+1,若年齡超過必定限制(15),則被晉升到老年態。即長期存活的對象進入老年態
  • 老年代滿了而沒法容納更多的對象,Minor GC 以後一般就會進行Full GC,Full GC 清理整個內存堆 – 包括年輕代和年老代
  • Major GC 發生在老年代的GC清理老年區,常常會伴隨至少一次Minor GC,比Minor GC慢10倍以上

JVM中的永久代中會發生垃圾回收嗎

垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。請參考下Java8:從永久代到元數據區
(譯者注:Java8中已經移除了永久代,新加了一個叫作元數據區的native內存區)

JAVA8 與元數據

在 Java8 中, 永久代已經被移除,被一個稱爲「元數據區」(元空間)的區域所取代。元空間的本質和永久代相似,元空間與永久代之間最大的區別在於: 元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制。 類的元數據放入native memory, 字符串池和類的靜態變量放入 java 堆中, 這樣能夠加載多少類的元數據就再也不由MaxPermSize 控制, 而由系統的實際可用空間來控制。

如何判斷對象能夠被回收?

判斷對象是否存活通常有兩種方式:

  • 引用計數:

每一個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時能夠回收。此方法簡單,沒法解決對象相互循環引用的問題。

  • 可達性分析(Reachability Analysis):

從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的,不可達對象。

引用計數法

在 Java 中,引用和對象是有關聯的。若是要操做對象則必須用引用進行。所以,很顯然一個簡單的辦法是經過引用計數來判斷一個對象是否能夠回收。簡單說,即一個對象若是沒有任何與之關聯的引用, 即他們的引用計數都不爲 0, 則說明對象不太可能再被用到,那麼這個對象就是可回收對象。

可達性分析

爲了解決引用計數法的循環引用問題, Java 使用了可達性分析的方法。經過一系列的「GC roots」對象做爲起點搜索。若是在「GC roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。要注意的是,不可達對象不等價於可回收對象, 不可達對象變爲可回收對象至少要通過兩次標記過程。兩次標記後仍然是可回收對象,則將面臨回收。

Minor GC與Full GC分別在何時發生?

新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC

垃圾收集算法有哪些類型?

GC最基礎的算法有三類: 標記 -清除算法、複製算法、標記-壓縮算法,咱們經常使用的垃圾回收器通常都採用分代收集算法。

標記 -清除算法,「標記-清除」(Mark-Sweep)算法,如它的名字同樣,算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。

複製算法,「複製」(Copying)的收集算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。

標記-壓縮算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存

分代收集算法,「分代收集」(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法

說一下 JVM 有哪些垃圾回收算法?

  • 標記-清除算法:標記無用對象,而後進行清除回收。缺點:效率不高,沒法清除垃圾碎片。
  • 複製算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活着的對象複製到另外一塊上,而後再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。
  • 標記-整理算法:標記無用對象,讓全部存活的對象都向一端移動,而後直接清除掉端邊界之外的內存。
  • 分代算法:根據對象存活週期的不一樣將內存劃分爲幾塊,通常是新生代和老年代,新生代基本採用複製算法,老年代採用標記整理算法。

標記-清除算法

標記無用對象,而後進行清除回收。

標記-清除算法(Mark-Sweep)是一種常見的基礎垃圾收集算法,它將垃圾收集分爲兩個階段:

  • 標記階段:標記出能夠回收的對象。
  • 清除階段:回收被標記的對象所佔用的空間。

標記-清除算法之因此是基礎的,是由於後面講到的垃圾收集算法都是在此算法的基礎上進行改進的。

優勢:實現簡單,不須要對象進行移動。

缺點:標記、清除過程效率低,產生大量不連續的內存碎片,提升了垃圾回收的頻率。

標記-清除算法的執行的過程以下圖所示

img

複製算法

爲了解決標記-清除算法的效率不高的問題,產生了複製算法。它把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾收集時,遍歷當前使用的區域,把存活對象複製到另一個區域中,最後將當前使用的區域的可回收的對象進行回收。

優勢:按順序分配內存便可,實現簡單、運行高效,不用考慮內存碎片。

缺點:可用的內存大小縮小爲原來的一半,對象存活率高時會頻繁進行復制。

複製算法的執行過程以下圖所示

img

標記-整理算法

在新生代中可使用複製算法,可是在老年代就不能選擇複製算法了,由於老年代的對象存活率會較高,這樣會有較多的複製操做,致使效率變低。標記-清除算法能夠應用在老年代中,可是它效率不高,在內存回收後容易產生大量內存碎片。所以就出現了一種標記-整理算法(Mark-Compact)算法,與標記-整理算法不一樣的是,在標記可回收的對象後將全部存活的對象壓縮到內存的一端,使他們緊湊的排列在一塊兒,而後對端邊界之外的內存進行回收。回收後,已用和未用的內存都各自一邊。

優勢:解決了標記-清理算法存在的內存碎片問題。

缺點:仍須要進行局部對象移動,必定程度上下降了效率。

標記-整理算法的執行過程以下圖所示

img

分代收集算法

分代收集法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的不一樣生命週期將內存劃分爲不一樣的域,通常狀況下將 GC 堆劃分爲老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特色是每次垃圾回收時只有少許對象須要被回收,新生代的特色是每次垃圾回收時都有大量垃圾須要被回收,所以能夠根據不一樣區域選擇不一樣的算法。

當前商業虛擬機都採用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據對象的存活週期將內存劃分爲幾塊。通常包括年輕代老年代永久代,如圖所示:

img

當前主流 VM 垃圾收集都採用」分代收集」 (Generational Collection)算法, 這種算法會根據對象存活週期的不一樣將內存劃分爲幾塊, 如 JVM 中的 新生代、老年代、永久代, 這樣就能夠根據各年代特色分別採用最適當的 GC 算法

新生代與複製算法

每次垃圾收集都能發現大批對象已死, 只有少許存活. 所以選用複製算法, 只須要付出少許存活對象的複製成本就能夠完成收集

目前大部分 JVM 的 GC 對於新生代都採起 Copying 算法,由於新生代中每次垃圾回收都要回收大部分對象,即要複製的操做比較少,但一般並非按照 1: 1 來劃分新生代。通常將新生代劃分爲一塊較大的 Eden 空間和兩個較小的 Survivor 空間(From Space, To Space),每次使用Eden 空間和其中的一塊 Survivor 空間,當進行回收時,將該兩塊空間中還存活的對象複製到另外一塊 Survivor 空間中。
在這裏插入圖片描述

老年代與標記複製算法

由於老年代對象存活率高、沒有額外空間對它進行分配擔保, 就必須採用「標記—清理」或「標記—整理」 算法來進行回收, 沒必要進行內存複製, 且直接騰出空閒內存。於是採用 Mark-Compact 算法。

  1. JAVA 虛擬機提到過的處於方法區的永生代(Permanet Generation), 它用來存儲 class 類,常量,方法描述等。對永生代的回收主要包括廢棄常量和無用的類。
  2. 對象的內存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放對象的那一塊),少數狀況會直接分配到老生代。
  3. 當新生代的 Eden Space 和 From Space 空間不足時就會發生一次 GC,進行 GC 後, EdenSpace 和 From Space 區的存活對象會被挪到 To Space,而後將 Eden Space 和 FromSpace 進行清理。
  4. 若是 To Space 沒法足夠存儲某個對象,則將這個對象存儲到老生代。
  5. 在進行 GC 後,使用的即是 Eden Space 和 To Space 了,如此反覆循環。
  6. 當對象在 Survivor 區躲過一次 GC 後,其年齡就會+1。 默認狀況下年齡到達 15 的對象會被移到老生代中。

GC 垃圾收集器

Java 堆內存被劃分爲新生代和年老代兩部分,新生代主要使用複製和標記-清除垃圾回收算法;年老代主要使用標記-整理垃圾回收算法,所以 java 虛擬中針對新生代和年老代分別提供了多種不一樣的垃圾收集器, JDK1.6 中 Sun HotSpot 虛擬機的垃圾收集器以下:

在這裏插入圖片描述

說一下 JVM 有哪些垃圾回收器?

若是說垃圾收集算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現。下圖展現了7種做用於不一樣分代的收集器,其中用於回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用於回收整個Java堆的G1收集器。不一樣收集器之間的連線表示它們能夠搭配使用。

img

  • Serial收集器(複製算法): 新生代單線程收集器,標記和清理都是單線程,優勢是簡單高效;

  • ParNew收集器 (複製算法): 新生代收並行集器,其實是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;

  • Parallel Scavenge收集器 (複製算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量能夠高效率的利用CPU時間,儘快完成程序的運算任務,適合後臺應用等對交互相應要求不高的場景;

  • Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;

  • Parallel Old收集器 (標記-整理算法): 老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

  • CMS(Concurrent Mark Sweep)收集器(標記-清除算法): 老年代並行收集器,以獲取最短回收停頓時間爲目標的收集器,具備高併發、低停頓的特色,追求最短GC回收停頓時間。

  • G1(Garbage First)收集器 (標記-整理算法): Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於「標記-整理」算法實現,也就是說不會產生內存碎片。此外,G1收集器不一樣於以前的收集器的一個重要特色是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

Serial 與 Parallel GC 之間的不一樣之處?

Serial 與 Parallel 在 GC 執行的時候都會引發 stop-the-world。它們之間主要不一樣 serial 收集器是默認的複製收集器,執行 GC 的時候只有一個線程,而parallel 收集器使用多個 GC 線程來執行。

相似的問題:你知道哪幾種垃圾收集器,各自的優缺點,重點講下cms和G1,包括原理,流程,優缺點。

思路: 必定要記住典型的垃圾收集器,尤爲cms和G1,它們的原理與區別,涉及的垃圾回收算法。

參考答案:

1)幾種垃圾收集器:

  • Serial收集器: 單線程的收集器,收集垃圾時,必須stop the world,使用複製算法。
  • ParNew收集器: Serial收集器的多線程版本,也須要stop the world,複製算法。
  • Parallel Scavenge收集器: 新生代收集器,複製算法的收集器,併發的多線程收集器,目標是達到一個可控的吞吐量。若是虛擬機總共運行100分鐘,其中垃圾花掉1分鐘,吞吐量就是99%。
  • Serial Old收集器: 是Serial收集器的老年代版本,單線程收集器,使用標記整理算法。
  • Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多線程,標記-整理算法。
  • CMS(Concurrent Mark Sweep) 收集器: 是一種以得到最短回收停頓時間爲目標的收集器,標記清除算法,運做過程:初始標記,併發標記,從新標記,併發清除,收集結束會產生大量空間碎片。
  • G1收集器: 標記整理算法實現,運做流程主要包括如下:初始標記,併發標記,最終標記,篩選標記。不會產生空間碎片,能夠精確地控制停頓。

2)CMS收集器和G1收集器的區別:

  • CMS收集器是老年代的收集器,能夠配合新生代的Serial和ParNew收集器一塊兒使用;

  • G1收集器收集範圍是老年代和新生代,不須要結合其餘收集器使用;

  • CMS收集器以最小的停頓時間爲目標的收集器;

  • G1收集器可預測垃圾回收的停頓時間

  • CMS收集器是使用「標記-清除」算法進行的垃圾回收,容易產生內存碎片

  • G1收集器使用的是「標記-整理」算法,進行了空間整合,下降了內存空間碎片。

詳細介紹一下 CMS 垃圾回收器?

CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量爲代價來得到最短回收停頓時間的垃圾回收器。對於要求服務器響應速度的應用上,這種垃圾回收器很是適合。在啓動 JVM 的參數加上「-XX:+UseConcMarkSweepGC」來指定使用 CMS 垃圾回收器。

CMS 使用的是標記-清除的算法實現的,因此在 gc 的時候回產生大量的內存碎片,當剩餘內存不能知足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會採用 Serial Old 回收器進行垃圾清除,此時的性能將會被下降。

Serial 垃圾收集器(單線程、 複製算法)

Serial(英文連續) 是最基本垃圾收集器,使用複製算法,曾經是JDK1.3.1 以前新生代惟一的垃圾收集器。 Serial 是一個單線程的收集器, 它不但只會使用一個 CPU 或一條線程去完成垃圾收集工做,而且在進行垃圾收集的同時,必須暫停其餘全部的工做線程,直到垃圾收集結束。
Serial 垃圾收集器雖然在收集垃圾過程當中須要暫停全部其餘的工做線程,可是它簡單高效,對於限定單個 CPU 環境來講,沒有線程交互的開銷,能夠得到最高的單線程垃圾收集效率,所以 Serial垃圾收集器依然是 java 虛擬機運行在 Client 模式下默認的新生代垃圾收集器。

ParNew 垃圾收集器(Serial+多線程)

ParNew 垃圾收集器實際上是 Serial 收集器的多線程版本,也使用複製算法,除了使用多線程進行垃圾收集以外,其他的行爲和 Serial 收集器徹底同樣, ParNew 垃圾收集器在垃圾收集過程當中一樣也要暫停全部其餘的工做線程。
ParNew 收集器默認開啓和 CPU 數目相同的線程數,能夠經過-XX:ParallelGCThreads 參數來限制垃圾收集器的線程數。 【Parallel:平行的】
ParNew 雖然是除了多線程外和Serial 收集器幾乎徹底同樣,可是ParNew垃圾收集器是不少 java虛擬機運行在 Server 模式下新生代的默認垃圾收集器。

Parallel Scavenge 收集器(多線程複製算法、高效)

Parallel Scavenge 收集器也是一個新生代垃圾收集器,一樣使用複製算法,也是一個多線程的垃圾收集器, 它重點關注的是程序達到一個可控制的吞吐量(Thoughput, CPU 用於運行用戶代碼的時間/CPU 總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)),高吞吐量能夠最高效率地利用 CPU 時間,儘快地完成程序的運算任務,主要適用於在後臺運算而不須要太多交互的任務。 自適應調節策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個重要區別。

Serial Old 收集器(單線程標記整理算法 )

Serial Old 是 Serial 垃圾收集器年老代版本,它一樣是個單線程的收集器,使用標記-整理算法,這個收集器也主要是運行在 Client 默認的
java 虛擬機默認的年老代垃圾收集器。在 Server 模式下,主要有兩個用途:

  1. 在 JDK1.5 以前版本中與新生代的 Parallel Scavenge 收集器搭配使用。
  2. 做爲年老代中使用 CMS 收集器的後備垃圾收集方案。新生代 Serial 與年老代 Serial Old 搭配垃圾收集過程圖:
    在這裏插入圖片描述
    新生代 Parallel Scavenge 收集器與 ParNew 收集器工做原理相似,都是多線程的收集器,都使用的是複製算法,在垃圾收集過程當中都須要暫停全部的工做線程。新生代 ParallelScavenge/ParNew 與年老代 Serial Old 搭配垃圾收集過程圖:
    在這裏插入圖片描述

Parallel Old 收集器(多線程標記整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多線程的標記-整理算法,在 JDK1.6纔開始提供。
在 JDK1.6 以前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保證新生代的吞吐量優先,沒法保證總體的吞吐量, Parallel Old 正是爲了在年老代一樣提供吞吐量優先的垃圾收集器, 若是系統對吞吐量要求比較高,能夠優先考慮新生代Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。
新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配運行過程圖
在這裏插入圖片描述

CMS 收集器(多線程標記清除算法)

Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾回收停頓時間, 和其餘年老代使用標記-整理算法不一樣,它使用多線程的標記-清除算法。最短的垃圾收集停頓時間能夠爲交互比較高的程序提升用戶體驗。CMS 工做機制相比其餘的垃圾收集器來講更復雜。整個過程分爲如下 4 個階段:

初始標記
只是標記一下 GC Roots 能直接關聯的對象,速度很快,仍然須要暫停全部的工做線程。
併發標記
進行 GC Roots 跟蹤的過程,和用戶線程一塊兒工做,不須要暫停工做線程。
從新標記
爲了修正在併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,仍然須要暫停全部的工做線程。
併發清除
清除 GC Roots 不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程。因爲耗時最長的併發標記和併發清除過程當中,垃圾收集線程能夠和用戶如今一塊兒併發工做, 因此整體上來看CMS 收集器的內存回收和用戶線程是一塊兒併發地執行。CMS 收集器工做過程

在這裏插入圖片描述

G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與 CMS 收集器, G1 收集器兩個最突出的改進是:
1.基於標記-整理算法,不產生內存碎片。
2.能夠很是精確控制停頓時間,在不犧牲吞吐量前提下,實現低停頓垃圾回收。G1 收集器避免全區域垃圾收集,它把堆內存劃分爲大小固定的幾個獨立區域,而且跟蹤這些區域的垃圾收集進度,同時在後臺維護一個優先級列表,每次根據所容許的收集時間, 優先回收垃圾最多的區域。區域劃分和優先級區域回收機制,確保 G1 收集器能夠在有限時間得到最高的垃圾收集效率

新生代垃圾回收器和老年代垃圾回收器都有哪些?有什麼區別?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代垃圾回收器通常採用的是複製算法,複製算法的優勢是效率高,缺點是內存利用率低;老年代回收器通常採用的是標記-整理的算法進行垃圾回收。

簡述分代垃圾回收器是怎麼工做的?

分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。

新生代使用的是複製算法,新生代裏有 3 個分區:Eden、To Survivor、From Survivor,它們的默認佔比是 8:1:1,它的執行流程以下:

  • 把 Eden + From Survivor 存活的對象放入 To Survivor 區;
  • 清空 Eden 和 From Survivor 分區;
  • From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。

每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。

老生代當空間佔用到達某個值以後就會觸發全局垃圾收回,通常使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的總體執行流程。

何時會觸發FullGC?

除直接調用System.gc外,觸發Full GC執行的狀況有以下四種。
1.舊生代空間不足
老生代空間只有在新生代對象轉入及建立爲大對象、大數組時纔會出現不足的現象,當執行Full GC後空間仍然不足,則拋出以下錯誤:
java.lang.OutOfMemoryError: Java heap space
爲避免以上兩種情況引發的FullGC,調優時應儘可能作到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。
2.Permanet Generation空間滿
PermanetGeneration中存放的爲一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation
可能會被佔滿,在未配置爲採用CMS GC的狀況下會執行Full GC。若是通過Full GC仍然回收不了,那麼JVM會拋出以下錯誤信息:
java.lang.OutOfMemoryError: PermGen space
爲避免Perm Gen佔滿形成Full GC現象,可採用的方法爲增大Perm Gen空間或轉爲使用CMS GC。
3.CMS GC時出現promotion failed和concurrent mode failure
對於採用CMS進行老生代GC的程序而言,尤爲要注意GC日誌中是否有promotion failed和concurrent mode failure兩種情況,當這兩種情況出現時可能會觸發Full GC。
promotionfailed是在進行Minor GC時,survivor space放不下、對象只能放入老生代,而此時老生代也放不下形成的;concurrent mode failure是在執行CMS GC的過程當中同時有對象要放入老生代,而此時老生代空間不足形成的。
應對措施爲:增大survivorspace、老生代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會因爲JDK的bug29致使CMS在remark完畢後好久才觸發sweeping動做。對於這種情況,可經過設置-XX:CMSMaxAbortablePrecleanTime=5(單位爲ms)來避免。
4.統計獲得的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間
這是一個較爲複雜的觸發狀況,Hotspot爲了不因爲新生代對象晉升到舊生代致使舊生代空間不足的現象,在進行Minor GC時,作了一個判斷,若是以前統計所獲得的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。
例如程序第一次觸發MinorGC後,有6MB的對象晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,若是小於6MB,則執行Full GC。
當新生代採用PSGC時,方式稍有不一樣,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大於6MB,如小於,則觸發對舊生代的回收。除了以上4種情況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,默認狀況下會一小時執行一次Full GC。可經過在啓動時經過- java-Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執行的間隔時間或經過-XX:+ DisableExplicitGC來禁止RMI調用System.gc

內存分配策略

簡述java內存分配與回收策率以及Minor GC和Major GC

所謂自動內存管理,最終要解決的也就是內存分配和內存回收兩個問題。前面咱們介紹了內存回收,這裏咱們再來聊聊內存分配。

對象的內存分配一般是在 Java 堆上分配(隨着虛擬機優化技術的誕生,某些場景下也會在棧上分配,後面會詳細介紹),對象主要分配在新生代的 Eden 區,若是啓動了本地線程緩衝,將按照線程優先在 TLAB 上分配。少數狀況下也會直接在老年代上分配。總的來講分配規則不是百分百固定的,其細節取決於哪種垃圾收集器組合以及虛擬機相關參數有關,可是虛擬機對於內存的分配仍是會遵循如下幾種「普世」規則:

對象優先在 Eden 區分配

多數狀況,對象都在新生代 Eden 區分配。當 Eden 區分配沒有足夠的空間進行分配時,虛擬機將會發起一次 Minor GC。若是本次 GC 後仍是沒有足夠的空間,則將啓用分配擔保機制在老年代中分配內存。

這裏咱們提到 Minor GC,若是你仔細觀察過 GC 平常,一般咱們還能從日誌中發現 Major GC/Full GC。

  • Minor GC 是指發生在新生代的 GC,由於 Java 對象大多都是朝生夕死,全部 Minor GC 很是頻繁,通常回收速度也很是快;
  • Major GC/Full GC 是指發生在老年代的 GC,出現了 Major GC 一般會伴隨至少一次 Minor GC。Major GC 的速度一般會比 Minor GC 慢 10 倍以上。

大對象直接進入老年代

所謂大對象是指須要大量連續內存空間的對象,頻繁出現大對象是致命的,會致使在內存還有很多空間的狀況下提早觸發 GC 以獲取足夠的連續空間來安置新對象。

前面咱們介紹過新生代使用的是標記-清除算法來處理垃圾回收的,若是大對象直接在新生代分配就會致使 Eden 區和兩個 Survivor 區之間發生大量的內存複製。所以對於大對象都會直接在老年代進行分配。

長期存活對象將進入老年代

虛擬機採用分代收集的思想來管理內存,那麼內存回收時就必須判斷哪些對象應該放在新生代,哪些對象應該放在老年代。所以虛擬機給每一個對象定義了一個對象年齡的計數器,若是對象在 Eden 區出生,而且可以被 Survivor 容納,將被移動到 Survivor 空間中,這時設置對象年齡爲 1。對象在 Survivor 區中每「熬過」一次 Minor GC 年齡就加 1,當年齡達到必定程度(默認 15) 就會被晉升到老年代。

對象分配規則

  1. 對象優先分配在Eden區,若是Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
  2. 大對象直接進入老年代(大對象是指須要大量連續內存空間的對象)。這樣作的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。
  3. 長期存活的對象進入老年代。虛擬機爲每一個對象定義了一個年齡計數器,若是對象通過了1次Minor GC那麼對象會進入Survivor區,以後每通過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。
  4. 動態判斷對象的年齡。若是Survivor區中相同年齡的全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象能夠直接進入老年代。
  5. 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,若是這個值大於老年區的剩餘值大小則進行一次Full GC,若是小於檢查HandlePromotionFailure設置,若是true則只進行Monitor GC,若是false則進行Full GC

虛擬機類加載機制

簡述java類加載機制?

虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,解析和初始化,最終造成能夠被虛擬機直接使用的java類型。

描述一下JVM加載Class文件的原理機制

Java中的全部類,都須要由類加載器裝載到JVM中才能運行。類加載器自己也是一個類,而它的工做就是把class文件從硬盤讀取到內存中。在寫程序的時候,咱們幾乎不須要關心類的加載,由於這些都是隱式裝載的,除非咱們有特殊的用法,像是反射,就須要顯式的加載所須要的類。

類裝載方式,有兩種 :

1.隱式裝載, 程序在運行過程當中當碰到經過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,

2.顯式裝載, 經過class.forname()等方法,顯式加載須要的類

Java類的加載是動態的,它並不會一次性將全部類所有加載後再運行,而是保證程序運行的基礎類(像是基類)徹底加載到jvm中,至於其餘類,則在須要的時候才加載。這固然就是爲了節省內存開銷。

描述一下 JVM 加載 class 文件的原理機制

JVM 中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java 中的類加載器是一個重要的 Java 運行時系統組件,它負責在運行時查找和裝入類文件中的類。
因爲 Java 的跨平臺性,通過編譯的 Java 源程序並非一個可執行程序,而是一個或多個類文件。當 Java 程序須要使用某個類時,JVM 會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。類的加載是指把類的.class 文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class 文件,而後產生與所加載類對應
的 Class 對象。

加載完成後,Class 對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後 JVM 對
類進行初始化,包括:1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;2)若是類中存在初始化語句,就依次執行這些初始化語句。
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader 的子類)。

從 Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM 更好的保證了 Java 平臺的安全性,在該機制中,JVM 自帶的Bootstrap 是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM 不會向 Java 程序提供對 Bootstrap 的引用。下面是關於幾個類
加載器的說明:

  1. Bootstrap:通常用本地代碼實現,負責加載 JVM 基礎核心類庫(rt.jar);
  2. Extension:從 java.ext.dirs 系統屬性所指定的目錄中加載類庫,它的父加載器是 Bootstrap;
  3. System:又叫應用類加載器,其父類是 Extension。它是應用最普遍的類加載器。它從環境變量 classpath 或者系統屬性
    java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

JVM 類加載機制

JVM 類加載機制分爲五個部分:加載,驗證,準備,解析,初始化,下面咱們就分別來看一下這五個過程。
在這裏插入圖片描述

加載
加載是類加載過程當中的一個階段, 這個階段會在內存中生成一個表明這個類的 java.lang.Class 對象, 做爲方法區這個類的各類數據的入口。注意這裏不必定非得要從一個 Class 文件獲取,這裏既能夠從 ZIP 包中讀取(好比從 jar 包和 war 包中讀取),也能夠在運行時計算生成(動態代理),也能夠由其它文件生成(好比將 JSP 文件轉換成對應的 Class 類)。
驗證
這一階段的主要目的是爲了確保 Class 文件的字節流中包含的信息是否符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。
準備
準備階段是正式爲類變量分配內存並設置類變量的初始值階段,即在方法區中分配這些變量所使用的內存空間。注意這裏所說的初始值概念,好比一個類變量定義爲:
實際上變量 v 在準備階段事後的初始值爲 0 而不是 8080, 將 v 賦值爲 8080 的 put static 指令是程序被編譯後, 存放於類構造器方法之中。
可是注意若是聲明爲:public static final int v = 8080;
在編譯階段會爲 v 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 v賦值爲 8080。
解析
解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。符號引用就是 class 文件中的:

public static int v = 8080;

實際上變量 v 在準備階段事後的初始值爲 0 而不是 8080, 將 v 賦值爲 8080 的 put static 指令是程序被編譯後, 存放於類構造器方法之中。可是注意若是聲明爲:
在編譯階段會爲 v 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 v
賦值爲 8080。解析
解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。符號引用就是 class 文件中的:

public static final int v = 8080;

在編譯階段會爲 v 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 v賦值爲 8080。
解析
解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。符號引用就是 class 文件中的:

  1. CONSTANT_Class_info
  2. CONSTANT_Field_info
  3. CONSTANT_Method_info
    等類型的常量。

符號引用
符號引用與虛擬機實現的佈局無關, 引用的目標並不必定要已經加載到內存中。 各類虛擬機實現的內存佈局能夠各不相同,可是它們能接受的符號引用必須是一致的,由於符號引用的字面量形式明肯定義在 Java 虛擬機規範的 Class 文件格式中。
直接引用
直接引用能夠是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。若是有了直接引用,那引用的目標一定已經在內存中存在。
初始化
初始化階段是類加載最後一個階段,前面的類加載階段以後,除了在加載階段能夠自定義類加載器之外,其它操做都由 JVM 主導。到了初始階段,纔開始真正執行類中定義的 Java 程序代碼。
類構造器
初始化階段是執行類構造器方法的過程。 方法是由編譯器自動收集類中的類變量的賦值操做和靜態語句塊中的語句合併而成的。虛擬機會保證子方法執行以前,父類的方法已經執行完畢, 若是一個類中沒有對靜態變量賦值也沒有靜態語句塊,那麼編譯器能夠不爲這個類生成() 方法。注意如下幾種狀況不會執行類初始化:

  1. 經過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。
  2. 定義對象數組,不會觸發該類的初始化。
  3. 常量在編譯期間會存入調用類的常量池中,本質上並無直接引用定義常量的類,不會觸發定義常量所在的類。
  4. 經過類名獲取 Class 對象,不會觸發類的初始化。
  5. 經過 Class.forName 加載指定類時,若是指定參數 initialize 爲 false 時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。
  6. 經過 ClassLoader 默認的 loadClass 方法,也不會觸發初始化動做。

什麼是類加載器,類加載器有哪些?

實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。

主要有一下四種類加載器:

  1. 啓動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,沒法被java程序直接引用。
  2. 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
  3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader()來獲取它。
  4. 用戶自定義類加載器,經過繼承 java.lang.ClassLoader類的方式實現。

說一下類裝載的執行過程?

類裝載分爲如下 5 個步驟:

  • 加載:根據查找路徑找到相應的 class 文件而後導入;
  • 驗證:檢查加載的 class 文件的正確性;
  • 準備:給類中的靜態變量分配內存空間;
  • 解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解爲一個標示,而在直接引用直接指向內存中的地址;
  • 初始化:對靜態變量和靜態代碼塊執行初始化工做。

什麼是雙親委派模型?

在介紹雙親委派模型以前先說下類加載器。對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立在 JVM 中的惟一性,每個類加載器,都有一個獨立的類名稱空間。類加載器就是根據指定全限定名稱將 class 文件加載到 JVM 內存,而後再轉化爲 class 對象。

img

類加載器分類:

  • 啓動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載Java_HOME/lib/目錄中的,或者被 -Xbootclasspath 參數所指定的路徑中而且被虛擬機識別的類庫;
  • 其餘類加載器:
  • 擴展類加載器(Extension ClassLoader):負責加載\lib\ext目錄或Java. ext. dirs系統變量指定的路徑中的全部類庫;
  • 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,咱們能夠直接使用這個類加載器。通常狀況,若是咱們沒有自定義類加載器默認就是用這個加載器。

雙親委派模型:若是一個類加載器收到了類加載的請求,它首先不會本身去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣全部的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載沒法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。

當一個類收到了類加載請求時,不會本身先去加載這個類,而是將其委派給父類,由父類去加載,若是此時父類不能加載,反饋給子類,由子類去完成類的加載。

簡單說說你瞭解的類加載器,能夠打破雙親委派麼,怎麼打破。

思路: 先說明一下什麼是類加載器,能夠給面試官畫個圖,再說一下類加載器存在的意義,說一下雙親委派模型,最後闡述怎麼打破雙親委派模型。

參考的答案:

  1. 什麼是類加載器?

類加載器 就是根據指定全限定名稱將class文件加載到JVM內存,轉爲Class對象。

  • 啓動類加載器(Bootstrap ClassLoader):由C++語言實現(針對HotSpot),負責將存放在<JAVA_HOME>\lib目錄或-Xbootclasspath參數指定的路徑中的類庫加載到內存中。
  • 其餘類加載器:由Java語言實現,繼承自抽象類ClassLoader。如:
  • 擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\lib\ext目錄或java.ext.dirs系統變量指定的路徑中的全部類庫。
  • 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,咱們能夠直接使用這個類加載器。通常狀況,若是咱們沒有自定義類加載器默認就是用這個加載器。

2)雙親委派模型

雙親委派模型工做過程是:

若是一個類加載器收到類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每一個類加載器都是如此,只有當父加載器在本身的搜索範圍內找不到指定的類時(即ClassNotFoundException),子加載器纔會嘗試本身去加載。

雙親委派模型圖:

img

3)爲何須要雙親委派模型?

在這裏,先想一下,若是沒有雙親委派,那麼用戶是否是能夠本身定義一個java.lang.Object的同名類java.lang.String的同名類,並把它放到ClassPath中,那麼類之間的比較結果及類的惟一性將沒法保證,所以,爲何須要雙親委派模型?防止內存中出現多份一樣的字節碼

4)怎麼打破雙親委派模型?

打破雙親委派機制則不只要繼承ClassLoader類,還要重寫loadClass和findClass方法。

什麼是Java虛擬機?爲何Java被稱做是「平臺無關的編程語言」?

Java虛擬機是一個能夠執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。 Java被設計成容許應用程序能夠運行在任意的平臺,而不須要程序員爲每個平臺單獨重寫或者是從新編譯。Java虛擬機讓這個變爲可能,由於它知道底層硬件平臺的 指令長度和其餘特性。

JVM調優

說一下 JVM 調優的工具?

JDK 自帶了不少監控工具,都位於 JDK 的 bin 目錄下,其中最經常使用的是 jconsole 和 jvisualvm 這兩款視圖監控工具。

  • jconsole:用於對 JVM 中的內存、線程和類等進行監控;
  • jvisualvm:JDK 自帶的全能分析工具,能夠分析:內存快照、線程快照、程序死鎖、監控內存的變化、gc 變化等。

經常使用的 JVM 調優的參數都有哪些?

  • -Xms2g:初始化推大小爲 2g;
  • -Xmx2g:堆最大內存爲 2g;
  • -XX:NewRatio=4:設置年輕的和老年代的內存比例爲 1:4;
  • -XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例爲 8:2;
  • –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
  • -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
  • -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
  • -XX:+PrintGC:開啓打印 gc 信息;
  • -XX:+PrintGCDetails:打印 gc 詳細信息。

調優命令有哪些?

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  1. jps,JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。
  2. jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
  3. jmap,JVM Memory Map命令用於生成heap dump文件
  4. jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看
  5. jstack,用於生成java虛擬機當前時刻的線程快照。
  6. jinfo,JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數

調優工具

經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。

  1. jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存, 線程和類等的監控
  2. jvisualvm,jdk自帶全能工具,能夠分析內存快照、線程快照;監控內存變化、GC變化等。
  3. MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Javaheap分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗
  4. GChisto,一款專業分析gc日誌的工具

說說你知道的幾種主要的JVM參數

思路: 能夠說一下堆棧配置相關的,垃圾收集器相關的,還有一下輔助信息相關的。

參考答案:

1)堆棧配置相關

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0

-Xmx3550m: 最大堆大小爲3550m。

-Xms3550m: 設置初始堆大小爲3550m。

-Xmn2g: 設置年輕代大小爲2g。

-Xss128k: 每一個線程的堆棧大小爲128k。

-XX:MaxPermSize: 設置持久代大小爲16m

-XX:NewRatio=4: 設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。

-XX:SurvivorRatio=4: 設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6

-XX:MaxTenuringThreshold=0: 設置垃圾最大年齡。若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代。

2)垃圾收集器相關

-XX:+UseParallelGC-XX:ParallelGCThreads=20-XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5-XX:+UseCMSCompactAtFullCollection:

-XX:+UseParallelGC: 選擇垃圾收集器爲並行收集器。

-XX:ParallelGCThreads=20: 配置並行收集器的線程數

-XX:+UseConcMarkSweepGC: 設置年老代爲併發收集。

-XX:CMSFullGCsBeforeCompaction:因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此值設置運行多少次GC之後對內存空間進行壓縮、整理。

-XX:+UseCMSCompactAtFullCollection: 打開對年老代的壓縮。可能會影響性能,可是能夠消除碎片

3)輔助信息相關

-XX:+PrintGC-XX:+PrintGCDetails

-XX:+PrintGC 輸出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails 輸出形式:

[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

怎麼打出線程棧信息。

思路: 能夠說一下jps,top ,jstack這幾個命令,再配合一次排查線上問題進行解答。

參考答案:

  • 輸入jps,得到進程號。
  • top -Hp pid 獲取本進程中全部線程的CPU耗時性能
  • jstack pid命令查看當前java進程的堆棧狀態
  • 或者 jstack -l > /tmp/output.txt 把堆棧信息打到一個txt文件。
  • 可使用fastthread 堆棧定位,fastthread.io/
相關文章
相關標籤/搜索