科技改變生活 · 科技引領未來
1.什么是Java虛擬機?為什么Java被稱作是“平臺無關的編程語言”?Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。Java被設計成允許應用程序可以運行在任意的平臺,而不
1.什么是 Java 虛擬機?為什么 Java 被稱作是“平臺無關的編程語言”?
Java 虛擬機是一個可以執行 Java 字節碼的虛擬機進程。Java 源文件被編譯成能被 Java 虛擬機執行的字節碼文件。Java 被設計成允許應用程序可以運行在任意的平臺,而不需要程序員為每一個平臺單獨重寫或者是重新編譯。Java 虛擬機讓這個變為可能,因為它知道底層硬件平臺的指令長度和其他特性。
2.Java 內存結構?
方法區和對是所有線程共享的內存區域;而 java 棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。
3.內存模型以及分區,需要詳細到每個區放什么?
JVM 分為堆區和棧區,還有方法區,初始化的對象放在堆里面,引用放在棧里面,class 類信息常量池(static 常量和 static 變量)等放在方法區。 new:
4.堆里面的分區:Eden,survival (from+ to),老年代,各自的特點?
堆里面分為新生代和老生代(java8 取消了永久代,采用了 metaspace),新生代包含 Eden+Survivor 區,survivor 區里面分為 from 和 to 區,內存回收時,如果用的是復制算法,從 from 復制到 to,當經過一次或者多次 GC 之后,存活下來的對象會被移動到老年區,當 JVM 內存不夠用的時候,會觸發 Full GC,清理 JVM 老年區當新生區滿了之后會觸發 YGC,先把存活的對象放到其中一個 Survice 區,然后進行垃圾清理。
因為如果僅僅清理需要刪除的對象,這樣會導致內存碎片,因此一般會把 Eden 進行完全的清理,然后整理內存。
那么下次 GC 的時候,就會使用下一個 Survive,這樣循環使用。
如果有特別大的對象,新生代放不下,就會使用老年代的擔保,直接放到老年代里面。 因為 JVM 認為,一般大對象的存活時間一般比較久遠。
5 .解釋內存中的棧(stack)、堆(heap)和方法區(method area)的用法
通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用 JVM 中的棧空間;而通過 new 關鍵字和構造器創建的對象則放在堆空間,堆是垃圾收集器管理的主要區域,由于現在的垃圾收集器都采用分代收集算法,所以堆空間還可以細分為新生代和老生代,再具體一點可以分為 Eden、Survivor(又可分為 From Survivor 和 To Survivor)、Tenured;方法區和堆都是各個線程共享的內存區域,用于存儲已經被 JVM 加載的類信息、常量、靜態變量、JIT 編譯器編譯后的代碼等數據;程序中的字面量(literal)如直接書寫的 100、”hello”和常量都是放在常量池中,常量池是方法區的一部分,。棧空間操作起來最快但是棧很小,通常大量的對象都是放在堆空間,棧和堆的大小都可以通過 JVM 的啟動參數來進行調整,棧空間用光了會引發 StackOverflowError,而堆和常量池空間不足則會引發 OutOfMemoryError。
String str = new String("hello");
上面的語句中變量 str 放在棧上,用 new 創建出來的字符串對象放在堆上,而”hello”這個字面量是放在方法區的。
補充 1:較新版本的 Java(從 Java 6 的某個更新開始)中,由于 JIT 編譯器的發展和”逃逸分析”技術的逐漸成熟,棧上分配、標量替換等優化技術使得對象一定分配在堆上這件事情已經變得不那么絕對了。
補充 2:運行時常量池相當于 Class 文件常量池具有動態性,Java 語言并不要求常量一定只有編譯期間才能產生,運行期間也可以將新的常量放入池中,String 類的 intern()方法就是這樣的。看看下面代碼的執行結果是什么并且比較一下 Java 7 以前和以后的運行結果是否一致。
String s1 = new StringBuilder("go") .append("od").toString(); System.out.println(s1.intern() == s1); String s2 = new StringBuilder("ja") .append("va").toString(); System.out.println(s2.intern() == s2);
6.GC 的兩種判定方法?
引用計數法:指的是如果某個地方引用了這個對象就+1,如果失效了就-1,當為 0 就 會回收但是 JVM 沒有用這種方式,因為無法判定相互循環引用(A 引用 B,B 引用 A) 的情況。 引用鏈法:通過一種 GC ROOT 的對象(方法區中靜態變量引用的對象等-static 變 量)來判斷,如果有一條鏈能夠到達 GC ROOT 就說明,不能到達 GC ROOT 就說明 可以回收
7.SafePoint 是什么?
比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執行 GC 1.循環的末尾 (防止大循環的時候一直不進入 safepoint,而其他線程在等待它進入 safepoint) 2.方法返回前 3.調用方法的 call 之后 4.拋出異常的位置
8.GC 的三種收集方法:標記清除、標記整理、復制算法的原理與特點,分別用在什么地方,如果讓你優化收集方法,有什么思路?
先標記,標記完畢之后再清除,效率不高,會產生碎片 復制算法:分為 8:1 的 Eden 區和 survivor 區,就是上面談到的 YGC 標記整理:標記完畢之后,讓所有存活的對象向一端移動
9.GC 收集器有哪些?CMS 收集器與 G1 收集器的特點?
并行收集器:串行收集器使用一個單獨的線程進行收集,GC 時服務有停頓時間 串行收集器:次要回收中使用多線程來執行 CMS 收集器是基于“標記—清除”算法實現的,經過多次標記才會被清除 G1 從整體來看是基于“標記—整理”算法實現的收集器,從局部(兩個 Region 之間)上來看是基于“復制”算法實現的
10.Minor GC 與 Full GC 分別在什么時候發生?
新生代內存不夠用時候發生 MGC 也叫 YGC,JVM 內存不夠的時候發生 FGC
11. 幾種常用的內存調試工具:jmap、jstack、jconsole、jhat?
jstack 可以看當前棧的情況,jmap 查看內存,jhat 進行 dump 堆的信息 mat(eclipse 的也要了解一下)
12.什么是類的加載
類的加載指的是將類的.class 文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然后在堆區創建一個 java.lang.Class 對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位于堆區中的 Class 對象,Class 對象封裝了類在方法區內的數據結構,并且向 Java 程序員提供了訪問方法區內的數據結構的接口。
13.類加載器
14.描述一下 JVM 加載 class 文件的原理機制?
JVM 中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java 中的類加載器是一個重要的 Java 運行時系統組件,它負責在運行時查找和裝入類文件中的類。
由于 Java 的跨平臺性,經過編譯的 Java 源程序并不是一個可執行程序,而是一個或多個類文件。當 Java 程序需要使用某個類時,JVM 會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。類的加載是指把類的.class 文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class 文件,然后產生與所加載類對應的 Class 對象。加載完成后,Class 對象還不完整,所以此時的類還不可用。當類被加載后就進入連接階段,這一階段包括驗證、準備(為靜態變量分配內存并設置默認的初始值)和解析(將符號引用替換為直接引用)三個步驟。最后 JVM 對類進行初始化,包括:
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader 的子類)。
從 Java 2(JDK 1.2)開始,類加載過程采取了父親委托機制(PDM)。PDM 更好的保證了 Java 平臺的安全性,在該機制中,JVM 自帶的 Bootstrap 是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載。JVM 不會向 Java 程序提供對 Bootstrap 的引用。下面是關于幾個類加載器的說明:
15.Java 對象創建過程
1.JVM 遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。然后加載這個類(類加載過程在后邊講)
2.為對象分配內存。一種辦法“指針碰撞”、一種辦法“空閑列表”,最終常用的辦法“本地線程緩沖分配(TLAB)”
3.將除對象頭外的對象內存空間初始化為 0
4.對對象頭進行必要設置
16.類的生命周期
類的生命周期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;
java 類加載需要經歷以下 幾個過程:
加載時類加載的第一個過程,在這個階段,將完成以下三件事情:
1.通過一個類的全限定名獲取該類的二進制流。
2.將該二進制流中的靜態存儲結構轉化為方法去運行時數據結構。
3.在內存中生成該類的 Class 對象,作為該類的數據訪問入口。
驗證的目的是為了確保 Class 文件的字節流中的信息不回危害到虛擬機.在該階段主要完成以下四鐘驗證:
文件格式驗證:驗證字節流是否符合 Class 文件的規范,如主次版本號是否在當前虛擬機范圍內,常量池中的常量是否有不被支持的類型.
元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
字節碼驗證:是整個驗證過程中最復雜的一個階段,通過驗證數據流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗證。 如: 方法中的類型轉換是否正確,跳轉指令是否正確等。
符號引用驗證: 這個動作在后面的解析過程中發生,主要是為了確保解析動作能正確執行。
準備階段是為類的靜態變量分配內存并將其初始化為默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨著對象一起分配在 Java 堆中。
public static int value=123; //在準備階段 value 初始值為 0 。 在初始化階段才會變為 123。
該階段主要完成符號引用到直接引用的轉換動作。解析動作并不一定在初始化動作完成之前,也有可能在初始化之后。
初始化時類加載的最后一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。 到了初始化階段,才真正開始執行類中定義的 Java 程序。
17.簡述 java 類加載機制?
虛擬機把描述類的數據從 Class 文件加載到內存,并對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的 java 類型。
18.Java 對象結構
Java 對象由三個部分組成:對象頭、實例數據、對齊填充。
對象頭由兩部分組成,第一部分存儲對象自身的運行時數據:哈希碼、GC 分代年齡、鎖標識狀態、線程持有的鎖、偏向線程 ID(一般占 32/64 bit)。
第二部分是指針類型,指向對象的類元數據類型(即對象代表哪個類)。如果是數組對象,則對象頭中還有一部分用來記錄數組長度。
實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)
對齊填充:JVM 要求對象起始地址必須是 8 字節的整數倍(8 字節對齊)
19.Java 對象的定位方式
句柄池、直接指針。
20.如和判斷一個對象是否存活?(或者 GC 對象的判定方法)
判斷一個對象是否存活有兩種方法:
1.引用計數法
所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器為零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收。 引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象 A 引用對象 B,對象 B 又引用者對象 A,那么此時 A,B 對象的引用計數器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。
2.可達性算法(引用鏈法)
該算法的思想是:從一個被稱為 GC Roots 的對象開始向下搜索,如果一個對象到 GC Roots 沒有任何引用鏈相連時,則說明此對象不可用。 在 java 中可以作為 GC Roots 的對象有以下幾種: 虛擬機棧中引用的對象
方法區類靜態屬性引用的對象
方法區常量池引用的對象
本地方法棧 JNI 引用的對象
雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root 時,這個對象并不會立馬被回收,而是出于一個死緩的階段,若要被真正的回收需要經歷兩次標記如果對象在可達性分析中沒有與 GC Root 的引用鏈,那么此時就會被第一次標記并且進行一次篩選,篩選的條件是是否有必要執行 finalize()方法。當對象沒有覆蓋 finalize()方法或者已被虛擬機調用過,那么就認為是沒必要的。
如果該對象有必要執行 finalize()方法,那么這個對象將會放在一個稱為 F-Queue 的對隊列中,虛擬機會觸發一個 Finalize()線程去執行,此線程是低優先級的,并且虛擬機不會承諾一直等待它運行完,這是因為如果 finalize()執行緩慢或者發生了死鎖,那么就會造成 FQueue 隊列一直等待,造成了內存回收系統的崩潰。 GC 對處于 F-Queue 中的對象進行第二次被標記,這時,該對象將被移除”即將回收”集合,等待回收。
21.JVM 的永久代中會發生垃圾回收么?
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免 Full GC 是非常重要的原因。請參考下 Java8:從永久代到元數據區 (注:Java8 中已經移除了永久代,新加了一個叫做元數據區的 native 內存區)
22.簡述 java 內存分配與回收策率以及 Minor GC 和 Major GC?
1.對象優先在堆的 Eden 區分配。
2.大對象直接進入老年代.
3.長期存活的對象將直接進入老年代.,當 Eden 區沒有足夠的空間進行分配時,虛擬機會執行一次 Minor GC.Minor Gc 通常發生在新生代的 Eden 區,在這個區的對象生存期短,往往發生 Gc 的頻率較高, 回收速度比較快;Full Gc/Major GC 發生在老年代,一般情況下,觸發老年代 GC 的時候不會觸發 Minor GC,但是通過配置,可以在 Full GC 之前進行一次 MinorGC 這樣可以加快老年代的回收速度。
23.判斷一個對象應該被回收
該對象沒有與 GC Roots 相連
該對象沒有重寫 finalize()方法或 finalize()已經被執行過則直接回收(第一次標記)、否則將對象加入到 F-Queue 隊列中(優先級很低的隊列)在這里 finalize()方法被執行,之后進行第二次標記,如果對象仍然應該被 GC 則 GC,否則移除隊列。(在 finalize 方法中,對象很可能和其他 GC Roots 中的某一個對象建立了關聯,finalize 方法只會被調用一次,且不推薦使用 finalize 方法)
24.回收方法區
方法區回收價值很低,主要回收廢棄的常量和無用的類。
如何判斷無用的類:
該類所有實例都被回收(Java 堆中沒有該類的對象)
加載該類的 ClassLoader 已經被回收
該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方利用反射訪問該類
25.垃圾收集算法
GC 最基礎的算法有三種:標記 -清除算法、復制算法、標記-壓縮算法,我們常用的垃圾回收器一般都采用分代收集算法。
26.垃圾回收器
Serial 收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。
ParNew 收集器,ParNew 收集器其實就是 Serial 收集器的多線程版本。
Parallel 收集器,Parallel Scavenge 收集器類似 ParNew 收集器,Parallel 收集器更關注系統的吞吐量。
Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多線程和“標記-整理”算法
CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。
G1 收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足 GC 停頓時間要求的同時,還具備高吞吐量性能特征
27.GC 日志分析
摘錄 GC 日志一部分(前部分為年輕代 gc 回收;后部分為 full gc 回收):
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
通過上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen 屬于 Parallel 收集器。其中 PSYoungGen 表示 gc 回收前后年輕代的內存變化;ParOldGen 表示 gc 回收前后老年代的內存變化;PSPermGen 表示 gc 回收前后永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,因此一般盡量減少 full gc 的次數
28.調優命令
Sun JDK 監控和故障處理命令有 jps jstat jmap jhat jstack jinfo
29.調優工具
常用調優工具分為兩類,jdk 自帶監控工具:jconsole 和 jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
jconsole,Java Monitoring and Management Console 是從 java5 開始,在 JDK 中自帶的 java 監控和管理控制臺,用于對 JVM 中內存,線程和類等的監控
jvisualvm,jdk 自帶全能工具,可以分析內存快照、線程快照;監控內存變化、GC 變化等。
MAT,Memory Analyzer Tool,一個基于 Eclipse 的內存分析工具,是一個快速、功能豐富的 Java heap 分析工具,它可以幫助我們查找內存泄漏和減少內存消耗
GChisto,一款專業分析 gc 日志的工具
30.Minor GC 與 Full GC 分別在什么時候發生?
新生代內存不夠用時候發生 MGC 也叫 YGC,JVM 內存不夠的時候發生 FGC
金同