?1. JVM組成
1.1 JVM由哪些部分組成?運行流程?
- Java Virtual Machine:Java 虛擬機,Java程序的運行環境(java二進制字節碼的運行環境)
- 好處:一次編寫,到處運行;自動內存管理,垃圾回收機制
- 程序運行之前,需要先通過編譯器將 Java 源代碼文件編譯成 Java 字節碼文件;
- 程序運行時,JVM 會對字節碼文件進行逐行解釋,翻譯成機器碼指令,并交給對應的操作系統去執行。
?
?
好處:一次編寫,到處運行;自動內存管理,垃圾回收機制
JVM? <--->?操作系統(windows、linux)<--->?計算機硬件(cpu、內存條)
java跨平臺是因JVM屏蔽了操作系統的差異,真正運行代碼的不是操作系統
?
?
- JVM 主要由四個部分組成: 運行流程:
- Java 編譯器(javac)將 Java 代碼轉換為字節碼(.class 文件)
- 1. 類加載器(ClassLoader)
- 負責加載 .class 文件,將 Java 字節碼加載到內存中,并交給 JVM 執行
- 2. 運行時數據區(Runtime Data Area)
- 管理JVM使用的內存。主要包括:
- 方法區(Method Area):存儲類的元數據、常量、靜態變量等。
- 堆(Heap):存儲所有對象和數組,垃圾回收器主要回收堆中的對象。
- 棧(Stack):每個線程都有一個棧,用于存儲局部變量、方法調用等信息。
- 程序計數器(PC Register):每個線程有一個程序計數器,指示當前線程正在執行的字節碼指令地址。
- 本地方法棧(Native Method Stack):支持本地方法的調用(通過 JNI)。
- 其中方法區和堆是線程共享的,虛擬機棧、本地方法棧和程序計數器是線程私有的。
- 3. 執行引擎(Execution Engine)
- 負責執行字節碼,包含:
- 解釋器:逐條解釋執行字節碼。
- JIT 編譯器:將熱點代碼編譯為機器碼,提高執行效率。
- 垃圾回收器:回收堆中的不再使用的對象,釋放內存。
- 4. 本地庫接口(Native Method Library)
- 允許 Java 程序通過 java本地接口JNI(Java Native Interface)調用本地方法(如 C/C++ 編寫的代碼),與底層系統或硬件交互。
?1.2 什么是程序計數器
- 程序計數器:線程私有的,每個線程一份,內部保存字節碼的行號。用于記錄正在執行的字節碼指令的地址。
- 每個線程都有自己的程序計數器,確保線程切換時能夠繼續執行未完成的任務。
1.3 你能給我詳細的介紹java堆嗎?
- Java堆是 JVM 中用于存儲所有對象和數組的內存區域。線程共享的區域。當堆中沒有內存空間可分配給實例,也無法再擴展時,則拋出OutOfMemoryError異常。
- 它被分為:
- 年輕代(存儲新創建的對象),被劃分為三部分:
- Eden區:大多數新對象的分配區域;
- S0 和 S1(兩個大小嚴格相同的Survivor區):Eden 空間經過 GC 后存活下來的對象會被移到其中一個 Survivor 區域;
- 老年代:在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到老年代區間。
- 永久代:JDK 7 及之前,JVM 的方法區(也稱永久代),保存的類信息、靜態變量、常量、編譯后的代碼;
- 元空間:JDK 8 及之后,永久代被 Metaspace(元空間)取代,移除了永久代,把數據存儲到了本地內存的元空間中,且其大小不再受 JVM 堆的限制,防止內存溢出。
1.4 什么是虛擬機棧
Java Virtual machine Stacks (java 虛擬機棧)
- 每個線程在 JVM 中私有的一塊內存區域,稱為虛擬機棧,先進后出,用于存儲方法的局部變量和方法調用信息;
- 每個棧由多個棧幀(frame)組成,當線程執行方法時,為該方法分配一個棧幀(Stack Frame);
- 每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法;
垃圾回收是否涉及棧內存?
- 垃圾回收主要指就是堆內存,
- 棧內存中不會有垃圾回收的概念,因為棧內存是由 JVM 自動管理的,方法執行完成時,棧幀彈棧,內存就會釋放;
棧內存分配越大越好嗎?
- 未必,默認的棧內存通常為1024k;
- 棧內存過大會導致線程數變少,例如,機器總內存為512m,目前能活動的線程數則為512個,如果把棧內存改為2048k,那么能活動的線程數減半;
方法內的局部變量是否線程安全?
- 方法內的局部變量 本身是線程安全的,因為它們存儲在每個線程獨立的棧中,不會被其他線程共享。
- 但如果局部變量是引用類型,并且該引用指向的對象逃離了方法作用范圍(例如被返回或傳遞到外部),則需要考慮該對象的線程安全性。
- 如果對象是可變的,并且被多個線程訪問,可能會引發線程安全問題。
棧內存溢出情況(StackOverflowError)
棧幀過多導致棧內存溢出;
典型問題:遞歸調用會在棧中創建新的棧幀,如果遞歸深度過大,可能會導致棧空間耗盡,從而拋出
棧幀過大導致棧內存溢出
堆棧的區別是什么?
- 棧內存用來存儲局部變量和方法調用,但堆內存是用來存儲Java對象和數組的。
- 堆會GC垃圾回收,而棧不會;
- 棧內存是線程私有的,而堆內存是線程共有的;
- 兩者異常錯誤不同,但如果棧內存或者堆內存不足都會拋出異常
- 棧空間不足:java.lang.StackOverFlowError
- 堆空間不足:java.lang.OutOfMemoryError
1.5 說一下JVM運行時數據區
JVM 運行時數據區包括方法區、堆、棧、程序計數器和本地方法棧。
- 方法區存儲類的元數據、常量池、靜態變量和 JIT 編譯后的代碼。
- 類的結構信息:每個類的信息,如類名、父類名、接口、方法、字段的名稱和描述等。常量池:存儲常量值,如字符串常量、類常量等。靜態變量:屬于類的變量,而不是某個實例的變量。JIT編譯后的代碼是指 Jvm在運行時將熱點代碼從字節碼編譯為本地機器代碼。方法區在 JDK 7 之前被稱為 "永久代",從 JDK 8 開始,永久代被移除,改為使用元空間
- 堆是 JVM 中最大的內存區域,負責存儲所有的對象實例和數組,并進行垃圾回收。
- 棧存儲每個線程的局部變量、方法調用信息和返回地址等。
- 堆和棧是線程共享和線程私有的區域。
- 程序計數器是每個線程私有的,用于存儲當前線程正在執行的字節碼指令的地址。
- 本地方法棧支持 JNI 本地方法調用,線程私有。
- 專門為本地方法調用而設計。它用于執行本地代碼時所需的棧空間。
1.6? 能不能介紹一下方法區
- 方法區 是 JVM 運行時數據區的一部分,主要用于存儲類的信息、常量、靜態變量以及 JIT 編譯后的代碼。
- 在 JDK 7 之前,這部分內存稱為永久代(PermGen),而在 JDK 8 以后,永久代被移除,取而代之的是元空間(Metaspace),它位于本地內存中,不再受堆內存限制。
- 虛擬機啟動的時候創建,關閉虛擬機時釋放。
- 方法區的內存由 JVM 管理,并在類卸載時進行垃圾回收。
- 如果方法區域中的內存無法滿足分配請求,則會拋出OutOfMemoryError: Metaspac
1.7 介紹一下運行時常量池?
常量池
- 可以看作是一張表,虛擬機指令根據這張常量表找到要執行的類名、方法名、參數類型、字面量等信息
運行時常量池
- 常量池是?.class?文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,并把里面的符號地址變為真實地址
1.8 你聽過直接內存嗎?
- 它不受 JVM 內存回收管理,是虛擬機的系統內存;
- 常見于在 NIO 中使用直接內存,不需要在堆中開辟空間進行數據的拷貝,jvm可以直接操作直接內存,從而使數據讀寫傳輸更快。但分配回收成本較高。
- 使用傳統的IO的時間要比NIO操作的時間長了很多,也就說NIO的讀性能更好。
- 這個是跟我們的JVM的直接內存是有一定關系,如下圖,傳統阻塞IO的數據傳輸流程和NIO傳輸數據的流程。
2. 類加載器
2.1 什么是類加載器,類加載器有哪些?
JVM只會運行二進制文件,而類加載器(ClassLoader)的主要作用就是將.class字節碼文件加載到JVM內存,生成對應的Class對象,供程序使用。
- 啟動類加載器(BootStrap ClassLoader):
- 該類并不繼承ClassLoader類,其是由C++編寫實現。用于加載JAVA_HOME/jre/lib目錄下的類庫。
- 擴展類加載器(ExtClassLoader):
- 該類是ClassLoader的子類,主要加載JAVA_HOME/jre/lib/ext目錄中的類庫。
- 應用類加載器(AppClassLoader):
- 該類是ClassLoader的子類,主要用于加載classPath下的類,也就是加載開發者自己編寫的Java類。
- 自定義類加載器:
- 開發者自定義類繼承ClassLoader,實現自定義類加載規則。
類加載器的體系并不是“繼承”體系,而是委派體系,類加載器首先會到自己的parent中查找類或者資源,如果找不到才會到自己本地查找。類加載器的委托行為動機是為了避免相同的類被加載多次
2.2 什么是雙親委派模型?
- 雙親委派模型要求類加載器在加載某一個類時,先委托父加載器嘗試加載。
- 如果父加載器可以完成類加載任務,就返回成功;
- 只有父加載器無法加載時,子加載器才會加載。
2.3 JVM為什么采用雙親委派機制?
避免類的重復加載:父加載器加載的類,子加載器無需重復加載。
保證核心類庫的安全性:為了安全,保證類庫API不會被修改。如?
java.lang包下的類
只能由?啟動類加載器Bootstrap ClassLoader 加載,防止被篡改。
2.4 說一下類的生命周期
- 一個類從被加載到虛擬機內存中開始,到從內存中卸載,它的整個生命周期包括了:
加載、驗證、準備、解析、初始化、使用和卸載這7個階段。- 其中,驗證、準備和解析這三個部分統稱為連接(linking)。
2.5 說一下類裝載的執行過程?
類裝載過程包括三個階段:載入、連接和初始化,連接細分為 驗證、準備、解析,這是標準的 JVM 類裝載流程。
- 加載(Loading):通過類加載器找到 .class 文件讀取到內存,生成 Class 對象。
- 連接(Linking):
驗證:檢查字節碼是否合法,防止惡意代碼破壞 JVM;
準備:為類的靜態變量分配內存并設置默認初始值,但不執行賦值邏輯;
解析:將常量池中的 符號引用(如類名、方法名)轉為 直接引用(內存地址)。
- 初始化(Initialization):執行類的靜態代碼塊和靜態變量賦值。
在準備階段,靜態變量已經被賦過默認初始值了,在初始化階段,靜態變量將被賦值為代碼期望賦的值。比如說 static int a = 1;,在準備階段,a 的值為 0,在初始化階段,a 的值為 1
類裝載完成后的階段:加載完成后,類進入‘使用階段’。當 Class 對象不再被引用時,可能觸發‘卸載’。
- 使用:JVM 通過 Class 對象創建實例、調用方法,進入正常運行階段。
- 卸載:當 Class 對象不再被引用時,由 GC 回收,但 JVM 核心類(如 java.lang.*)不會被卸載。
3. 垃圾回收
3.1 簡述java垃圾回收機制?(GC是什么?為什么要GC)
- GC(Garbage Collection,垃圾回收)是 Java 中自動管理內存的機制,負責回收不再使用的對象,以釋放內存空間。
- 垃圾回收是 Java 程序員不需要顯式管理內存的一大優勢,它由 JVM 自動進行。
GC 的主要目的是:
自動管理內存:程序運行過程中會創建大量的對象,但一些對象在使用完后不再被引用。手動管理這些對象的內存釋放非常繁瑣且容易出錯;
防止內存泄漏:如果不及時釋放無用對象的內存,系統的可用內存會越來越少,最終可能導致 OutOfMemoryError;
避免內存溢出:GC 機制能夠保證內存不會因為長期積累未回收的對象而耗盡。
3.2?對象什么時候可以被垃圾器回收?
如果一個或多個對象沒有任何的引用指向它了,那么這個對象現在就是垃圾,如果定位了垃圾,則有可能會被垃圾回收器回收。
如果要定位什么是垃圾,有兩種方式來確定,
1. 引用計數法:通過計數引用的數量,當引用為 0 時回收。但不能處理循環引用
這種方法通過給每個對象維護一個引用計數器。當有一個新的引用指向該對象時,引用計數加 1;當引用離開時,計數減 1。
2. 可達性分析算法:通過檢查對象是否從根對象可達,無法訪問的對象可以回收。是 Java 使用的主要方法。
根對象是那些肯定不能當做垃圾回收的對象,就可以當做根對象
根對象包含:虛擬機棧中的局部變量;靜態變量;活動線程的引用;JNI 引用的對象。