java核心技术-jvm基础知识
文章目錄
- JVM回顧
- JVM、JRE、JDK之間關(guān)系?
- Java程序執(zhí)行過程?
- 面試官:解釋執(zhí)行和JIT(及時編譯)兩種執(zhí)行方式有什么區(qū)別?
- java虛擬機內(nèi)存管理
- jvm整體架構(gòu)
- JVM只是定義內(nèi)存劃分規(guī)范等,具體實現(xiàn)依賴不同虛擬機實現(xiàn),如HotSpot虛擬機
- jvm運行時內(nèi)存
- 程序計數(shù)器(PC寄存器)
- 面試官:程序計數(shù)器是什么?
- 面試官:java多線程如何實現(xiàn)的?多個線程同時執(zhí)行的?
- 虛擬機棧
- 面試官:什么是java虛擬機棧?
- 本地方法棧
- 堆
- 元空間
- 面試官:為什么要廢棄永久代,引入元空間?
- 方法區(qū)
- 面試官:元空間和方法區(qū)有什么區(qū)別?
- 運行時常量池
- 面試官:常量池和運行時常量池有什么區(qū)別?
- 直接內(nèi)存
- 面試官:你知道如何讀取大文件不保證內(nèi)存溢出?
- 面試官:給我講講什么是NIO以及實現(xiàn)原理?
- OOM異常
- JVM類加載機制
- 面試官:你知道一個java類從編譯到運行的全過程?
- java源碼編譯階段
- 面試官:給我講講類整個執(zhí)行過程?
- 類加載系統(tǒng)
- 類加載系統(tǒng)的執(zhí)行過程
- 面試官:給我講講類加載執(zhí)行順序以及各個階段作用?
- 加載
- 驗證
- 準(zhǔn)備
- 解析
- 面試官:“解析”一定要在“初始化”之前執(zhí)行?什么是“靜態(tài)綁定”和“動態(tài)綁定”?
- 初始化
- 面試官:給我講講類初始化階段 靜態(tài)變量、靜態(tài)代碼塊以及包含子類父類等初始化順序?
- 類加載器
- 面試官:Class對象和實例對象以及類之間的區(qū)別?
- 類加載器分類
- 雙親委派模型
- 什么是雙親委派模型?
- 如何自定義類加載器?
- 自定義類加載器
- ClassLoader源碼剖析
- 垃圾回收機制及算法
- 如何判斷對象已經(jīng)死亡?
- 面試:講講常見的垃圾收集算法?
- 面試:垃圾回收器了解?講講幾種垃圾回收器?
- 我理解的垃圾回收器
- Serial收集器
- ParNew收集器
- Parallel Scavenge收集器
- Serial Old收集器
- Parallel Old收集器
- CMS收集器
- 【面試必問】G1收集器
- 用過jvm調(diào)優(yōu)工具?
- jvm常用指令
- jvm常用工具
- Jconsole監(jiān)控管理工具
- VisualVM可視化工具
- 線上問題如何對GC日志分析?
- 理解GC日志參數(shù)
- GC日志分析方法
- GC日志分析工具
- 對生產(chǎn)環(huán)境jvm調(diào)優(yōu)過?
- tomcat
- jmeter
- 有過jvm參數(shù)優(yōu)化經(jīng)驗?你們生產(chǎn)環(huán)境jvm啟動參數(shù)如何配置對:
JVM回顧
JVM、JRE、JDK之間關(guān)系?
Java程序執(zhí)行過程?
面試官:解釋執(zhí)行和JIT(及時編譯)兩種執(zhí)行方式有什么區(qū)別?
我們一直在說 Java 字節(jié)碼是溝通 JVM 與 Java 程序的橋梁,下面使用 javap 來稍微看一下字節(jié)碼到底長什么樣子。
package net.dreamzuora.jvm;public class HelloWorld {public static void main(String[] args) {System.out.println("Hello World");} }查看字節(jié)碼指令步驟:
1.javac Helloword.java
2.javap -c HelloWorld
然后 JVM 會翻譯這些字節(jié)碼,它有兩種執(zhí)行方式。常見的就是解釋執(zhí)行,將 字節(jié)碼指令 + 操作數(shù)翻譯成機器代碼;
另外一種執(zhí)行方式就是 JIT,也就是我們常說的即時編譯,它會在一定條件下將字節(jié)碼編譯成機器碼之后再執(zhí)行。
編譯器是把源程序的每一條語句都編譯成機器語言,并保存成二進制文件,翻譯與執(zhí)行是分開的,這樣運行時計算機可以直接以機器語言來運行此程序,速度很快;C,C++都是靠編譯實現(xiàn)的。
解釋器則是只在執(zhí)行程序時,才一條一條的解釋成機器語言給計算機來執(zhí)行,翻譯與執(zhí)行一次性完成,所以運行速度是不如編譯后的程序運行的快的,但是就啟動效率而言,解釋執(zhí)行的速度更快,因為它不需要進行編譯過程
Java程序也需要編譯,但是沒有直接編譯稱為機器語言,而是編譯成為字節(jié)碼,然后在JVM上用解釋方式執(zhí)行字節(jié)碼。Python 的也采用了類似Java的編譯模式
Java通過解釋器解釋執(zhí)行字節(jié)碼,這樣的執(zhí)行方式相對較慢,尤其是遇到一些運行頻繁的代碼塊或者方法時。于是后來JVM引入了JIT即時編譯器(just in time),當(dāng)JVM發(fā)現(xiàn)某些代碼運行頻繁時就會認(rèn)定為熱點代碼“hot spot code”,為了提高運行效率,就會把這些代碼編譯成為平臺相關(guān)的機器碼然后進行優(yōu)化,JIT就是用來完成這項工作的。二者共同造就了java的優(yōu)勢——當(dāng)程序需要迅速啟動時,解釋器首先發(fā)揮作用,省去編譯時間,當(dāng)程序運行時,編譯器會逐漸將更多的代碼編譯成本地機器碼從而獲得更高的效率。
引用:《解釋執(zhí)行與編譯執(zhí)行以及JIT的區(qū)別》
java虛擬機內(nèi)存管理
jvm整體架構(gòu)
| 程序計數(shù)器(線程私有) | 字節(jié)碼運行的行號指令器 | 無 | 無 |
| 虛擬機棧(線程私有) | 存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息 | -Xss | StackOverflowError/ OutOfMemoryError |
| 堆 | 保存對象實例(包括數(shù)組) | -Xmn -Xms -Xsx | OutOfMemoryError |
| 方法區(qū) | 類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù) | -XX:PermSize:16M -XX:MaxPermSize:64MB | OutOfMemoryError |
| 本地方法棧 | 為虛擬機使用到的Native 方法服務(wù) | 無 | StackOverflowError/OutOfMemoryError |
JVM只是定義內(nèi)存劃分規(guī)范等,具體實現(xiàn)依賴不同虛擬機實現(xiàn),如HotSpot虛擬機
jvm運行時內(nèi)存
程序計數(shù)器(PC寄存器)
面試官:程序計數(shù)器是什么?
特點:
1.計算機上的PC寄存器是用來存放“偽指令”或地址,jvm中的pc寄存器(程序計算器)是一塊內(nèi)存用來存放將要執(zhí)行的指令地址
2.程序計數(shù)器是線程私有的,生命周期同線程生命周期,每個線程都有一個
3.該區(qū)域不回出現(xiàn)OOM
面試官:java多線程如何實現(xiàn)的?多個線程同時執(zhí)行的?
jvm通過給線程分配處理器執(zhí)行時間,使線程能夠執(zhí)行,但是一個處理器一次只能執(zhí)行一條指令,因此java多線程是通過線程切換的方式使得在同一時間段多個線程同時執(zhí)行,但是同一時刻一個處理器只能執(zhí)行一個線程。
因此為了多線程能夠順利切換,需要利用程序計數(shù)器來記錄每個線程運行的字節(jié)碼指令,并且屬于線程私有內(nèi)存,各個線程互不共享。
虛擬機棧
參考文章:
操作數(shù)棧詳解
動態(tài)鏈接、棧幀解釋
面試官:什么是java虛擬機棧?
切記:java虛擬機棧是線程私有的
本地方法棧
和java虛擬機棧類似只不過執(zhí)行的是native方法
堆
特點:
堆劃分:
jdk7:年輕代、老年代、永久代
jdk8:年輕代、老年代
GC:
分為部分GC和整個FULL GC
部分收集器: 不是完整收集java堆的的收集器,它又分為:
新生代收集(Minor GC / Young GC): 只是新生代的垃圾收集
老年代收集 (Major GC / Old GC): 只是老年代的垃圾收集 (CMS GC 單獨回收老年代)
混合收集(Mixed GC):收集整個新生代及老年代的垃圾收集 (G1 GC會混合回收, region區(qū)域回收)
整堆收集(Full GC):收集整個java堆和方法區(qū)的垃圾收集器
元空間
從 JDK 1.8 開始,移除永久代,并把方法區(qū)移至元空間,它位于本地內(nèi)存中,而不是虛擬機內(nèi)存中。
面試官:為什么要廢棄永久代,引入元空間?
這樣做的好處:
方法區(qū)
與Java堆一樣, 是各個線程共享的內(nèi)存區(qū)域, 它用于存儲已被虛擬機加載 的類型信息、常量、 靜態(tài)變量、 即時編譯器編譯后的代碼緩存等數(shù)據(jù)。
類型信息
對每個加載的類型(類Class、接口 interface、枚舉enum、注解 annotation),JVM必須在方法區(qū)中存儲以下類型信息:
- ① 這個類型的完整有效名稱(全名 = 包名.類名)
- ② 這個類型直接父類的完整有效名(對于 interface或是java.lang. Object,都沒有父類)
- ③ 這個類型的修飾符( public, abstract,final的某個子集)
- ④ 這個類型直接接口的一個有序列表
域信息
域信息,即為類的屬性,成員變量
JVM必須在方法區(qū)中保存類所有的成員變量相關(guān)信息及聲明順序。
域的相關(guān)信息包括:
域名稱、域類型、域修飾符(pυblic、private、protected、static、final、volatile、transient的
某個子集)
方法信息
JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序:
移地址、被捕獲的異常類的常量池索引
面試官:元空間和方法區(qū)有什么區(qū)別?
運行時常量池
面試官:常量池和運行時常量池有什么區(qū)別?
字節(jié)碼文件中,內(nèi)部包含了常量池
方法區(qū)中,內(nèi)部包含了運行時常量池
常量池:存放編譯期間生成的各種字面量與符號引用*
運行時常量池:常量池表在運行時的表現(xiàn)形式
編譯后的字節(jié)碼文件中包含了類型信息、域信息、方法信息等。通過ClassLoader將字節(jié)碼文件的常量池中的信息加
載到內(nèi)存中,存儲在了方法區(qū)的運行時常量池中。
讓我們來看看字節(jié)碼編譯后,查看常量池相關(guān)信息
package net.dreamzuora.jvm;public class HelloWorld {public HelloWorld() {}public static void main(String[] var0) {System.out.println("Hello World");} }反編譯:javap -verbose HelloWorld.class
字節(jié)碼文件除了包含了類的版本信息、方法、字段、接口等描述信息外,還包括常量池表:包括字面量、域、類型和方法的符號引用。
虛擬機指令根據(jù)常量池表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等類型。
指令解釋:ldc 把常量池中的項壓入棧、getstatic 從類中獲取靜態(tài)字段、調(diào)度對象的實現(xiàn)方法:invokevirtual
Javap與JVM指令解釋
為什么需要常量池?
public class Solution { public void method() { System.out.println("dreamzuora"); } }這段代碼很簡單,但是里面卻使用了 String、 System、 PrintStream及Object等結(jié)構(gòu)。如果代碼多,引用到的結(jié)構(gòu)會
更多!這里就需要常暈池,將這些引用轉(zhuǎn)變?yōu)榉栆?#xff0c;具體用到時,采取加載。
直接內(nèi)存
直接存儲大小不受JVM里內(nèi)存,它是直接利用操作系統(tǒng)內(nèi)存的堆外內(nèi)存。
面試官:你知道如何讀取大文件不保證內(nèi)存溢出?
利用MappedByteBuffer類來讀取,讀取方式
面試官:給我講講什么是NIO以及實現(xiàn)原理?
NIO:New Input/Output
NIO基于通道(Channel)和緩沖區(qū)(Buffer)的IO方式,通過Native方法直接分配堆外內(nèi)存,然后通過存儲在java堆中的DirectByteBuffer對象引用這個內(nèi)存地址進行操作,NIO之所以快這是通過這種方式直接訪問堆外內(nèi)存避免了java堆和native堆來回copy。
ByteBuffer:讀取虛擬機分配的堆內(nèi)存,受堆大小影響,會有OOM。
DirectByteBuffer:繼承ByteBuffer,但是直接訪問虛擬機物理內(nèi)存的類,
在訪問普通的ByteBuwer時,系統(tǒng)總是會使用一個“內(nèi)核緩沖區(qū)”進行操作。
而DirectBuwer所處的位置,就相當(dāng)于這個“內(nèi)核緩沖區(qū)”。因此,使用DirectBuwer是一種更加接近內(nèi)存底層的方法,所以它的速度比普通的ByteBuwer更快。(???這塊沒搞懂后期補充)
《Linux 內(nèi)核詳解以及內(nèi)核緩沖區(qū)技術(shù)》
OOM異常
JVM類加載機制
面試官:你知道一個java類從編譯到運行的全過程?
java源碼編譯階段->類加載階段->執(zhí)行階段
java源碼編譯階段
執(zhí)行:javac HelloWord.java將java->變成class文件
編譯階段做的三件事(這塊想深入理解可以去看《編譯原理》日后有時間會簡單做個總結(jié))
詳細(xì)過程:
查看字節(jié)碼信息:javap -c HelloWord.class
引用:
《編譯做了哪些事》
《java 編譯和加載和執(zhí)行類的全過程》
類加載階段以下內(nèi)容會講到
面試官:給我講講類整個執(zhí)行過程?
類加載系統(tǒng)
類加載系統(tǒng)的執(zhí)行過程
面試官:給我講講類加載執(zhí)行順序以及各個階段作用?
加載、驗證、準(zhǔn)備、解析、初始化、使用、卸載
簡要說明:
加載
加載階段的三件事:
1.獲取class文件二進制流(文件讀取操作rt.jar中)
2.將類信息、靜態(tài)變量、常量、字節(jié)碼放入方法區(qū)
3.在內(nèi)存中生成一個代表這個.class文件的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。一般
這個Class是在堆里的,不過HotSpot虛擬機比較特殊,這個Class對象是放在方法區(qū)中的
虛擬機規(guī)范對這三點的要求并不具體,因此虛擬機實現(xiàn)與具體應(yīng)用的靈活度都是相當(dāng)大的。例如第一條,根本沒有
指明二進制字節(jié)流要從哪里來、怎么來,因此單單就這一條,就能變出許多花樣來:
從數(shù)據(jù)庫中讀取,這種場景比較少見
驗證
.class文件不光是通過java編譯而來,可以通過任何手段生成class文件,例如利用十六進制編輯器生成等,因此為了驗證class文件是否合法,需要對class信息進行驗證。
準(zhǔn)備
為類的變量分配空間并初始賦值在方法區(qū)中,類的實例變量在初始化的時候賦值并分配在堆中。
這個階段賦初始值的變量指的是那些不被final修飾的static變量,比如"public static int value = 123",value在準(zhǔn)備階段過后是0而不是123,給value賦值為123的動作將在初始化階段才進行;比如"public static final int value = 123;"就不一樣了,在準(zhǔn)備階段,虛擬機就會給value賦值為123。
code-snippet 1 將會輸出 0,而 code-snippet 2將無法通過編譯。
切記
這是因為局部變量不像類變量那樣存在準(zhǔn)備階段。類變量有兩次賦初始值的過程,一次在準(zhǔn)備階段,賦予初始值(也可以是指定值);另外一次在初始化階段,賦予程序員定義的值。
因此,即使程序員沒有為類變量賦值也沒有關(guān)系,它仍然有一個默認(rèn)的初始值。但局部變量就不一樣了,如果沒有給它賦初始值,是不能使用的。
解析
解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。
符號引用和我們上面講的是一樣的,是對于類、變量、方法的描述。符號引用和虛擬機的內(nèi)存布
局是沒有關(guān)系的,引用的目標(biāo)未必已經(jīng)加載到內(nèi)存中了。
解析階段負(fù)責(zé)把整個類激活,串成一個可以找到彼此的網(wǎng),過程不可謂不重要。那這個階段都做了哪些工作呢?
面試官:“解析”一定要在“初始化”之前執(zhí)行?什么是“靜態(tài)綁定”和“動態(tài)綁定”?
加載、驗證、準(zhǔn)備、初始化、卸載這5個階段的順序是確定的,類的加載過程必須按照這種順序按部就班地
開始,而解析階段不一定:它在某些情況下可以初始化階段之后在開始,這是為了支持Java語言的運行時綁定(也稱為動態(tài)綁定)。
當(dāng)子類和父類(接口和實現(xiàn)類)存在同一個方法時,子類重寫父類(接口)方法時,程序在運行時調(diào)用的方法時,是調(diào)用父類(接口)的方法呢?還是調(diào)用子類的方法呢?我們將確定這種調(diào)用何種方法的操作稱之為綁定。
靜態(tài)綁定是在程序執(zhí)行前就已經(jīng)被綁定了(也就是在程序編譯過程中就已經(jīng)知道這個方法是哪個類中的方法)。
動態(tài)綁定:編譯器在每次調(diào)用方法時都要進行搜索,時間開銷相當(dāng)大。因此虛擬機會預(yù)先為每個類創(chuàng)建一個方發(fā)表(method table),其中列出了所有方法的簽名和實際調(diào)用的方法。
引用:《動態(tài)綁定和靜態(tài)綁定》
初始化
類初始化階段做的事情:
類的初始化和對象初始化?
cinit()方法:類構(gòu)造器對類的靜態(tài)變量、靜態(tài)代碼塊初始化過程
init()方法:調(diào)用類的構(gòu)造方法對實例對象進行初始化
首先讓我們類看看這段代碼:
public class InitDemo {static {i = 0;System.out.println(i);}static int i = 1;public static void main(String[] args) {} }第一個問題:這段代碼執(zhí)行結(jié)果是什么?
答案:會報錯。
那么第二個問題:這段代碼為什么會報錯,會拋出什么異常?
那么第三問題:為什么會拋出這個異常?
編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的, 靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量, 定義在它之后的變量, 在前面的靜態(tài)語句塊可以賦值, 但是不能訪問
面試官:給我講講類初始化階段 靜態(tài)變量、靜態(tài)代碼塊以及包含子類父類等初始化順序?
執(zhí)行步驟:
面試官:Java語言里,new表達(dá)式總體負(fù)責(zé)兩個動作?
引用文章:
《靜態(tài)變量、代碼塊、子類父類等順序》
《類初始化階段做的事》
類加載器
類加載器負(fù)責(zé)將class字節(jié)碼文件二進制流加載到內(nèi)存中,放入方法區(qū),并生成java.lang.Class對象的過程,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。
注意:JVM主要在程序第一次主動使用類的時候,才會去加載該類,也就是說,JVM并不是在一開始就把一個程序就所有的類都加載到內(nèi)存中,而是到不得不用的時候才把它加載進來,而且只加載一次。
面試官:Class對象和實例對象以及類之間的區(qū)別?
《javaClass對象詳解》
類加載器分類
BootstrapClassLoader引導(dǎo)類加載器、自定義類加載器(ExtensionClassLoader拓展類加載器,SystemClassLoader系統(tǒng)類加載器、用戶自定義類加載器)
作用:不同的類加載器負(fù)責(zé)加載不同路徑下的Class
《通俗易懂 啟動類加載器、擴展類加載器、應(yīng)用類加載器》
雙親委派模型
什么是雙親委派模型?
有了雙親委派模型,黑客自定義的 java.lang.String 類永遠(yuǎn)都不會被加載進內(nèi)存。因為首先是最頂端的類加載器加載系統(tǒng)的 java.lang.String 類,最終自定義的類加載器無法加載 java.lang.String 類。
或許你會想,我在自定義的類加載器里面強制加載自定義的 java.lang.String 類,不去通過調(diào)用父加載器不就好了嗎?確實,這樣是可行。但是,在 JVM 中,判斷一個對象是否是某個類型時,如果該對象的實際類型與待比較的類型的類加載器不同,那么會返回false。
如何自定義類加載器?
自定義類加載器
ClassLoader源碼剖析
垃圾回收機制及算法
如何判斷對象已經(jīng)死亡?
面試:講講常見的垃圾收集算法?
面試:垃圾回收器了解?講講幾種垃圾回收器?
我理解的垃圾回收器
Serial收集器
serial:Serial(串行)[?s??ri?l]收集器
ParNew收集器
Parallel Scavenge收集器
Parallel scavenge:Parallel(并行)[?p?r?lel] Scavenge(清除)[?sk?v?nd?]收集器
Serial Old收集器
Parallel Old收集器
CMS收集器
【面試必問】G1收集器
用過jvm調(diào)優(yōu)工具?
jvm常用指令
jvm常用工具
Jconsole監(jiān)控管理工具
VisualVM可視化工具
線上問題如何對GC日志分析?
理解GC日志參數(shù)
GC日志分析方法
GC日志分析工具
對生產(chǎn)環(huán)境jvm調(diào)優(yōu)過?
tomcat
jmeter
有過jvm參數(shù)優(yōu)化經(jīng)驗?你們生產(chǎn)環(huán)境jvm啟動參數(shù)如何配置對:
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的java核心技术-jvm基础知识的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转载保存】java操作HDFS
- 下一篇: 一对一聊天ajax实现