Java基礎知識
java是一個物件導向的,靜態型別,編譯執行,有VM/GC和執行時的跨平臺的高階語言。
一. 位元組碼技術
將寫好的java檔案編譯成class
javac .\TestJvm.java
檢視位元組碼
javap -c TestJVM
檢視更詳細的位元組碼
javap -c -verbose TestJVM
位元組碼的執行時結構
JVM是一個基於棧的計算機器。每個執行緒都有他所對應的執行緒棧,用於儲存棧幀。每一次方法呼叫,都會建立一個棧幀。
棧幀由運算元棧,區域性變數陣列以及一個Class引用(也叫動態連結)組成。
-
運算元棧:每個幀都包含了一個後入先出的棧,稱為運算元棧。
-
區域性變數表:用於存放方法引數和內部定義的區域性變數。區域性變數表的容量以變數槽(Slot)為單位,一個Slot只能存放一個boolean、byte、char、shoert、int、float、reference或returnAddress型別的資料
-
Class引用:指向當前方法在執行時常量池中對應的class
二、JVM類載入器
類的生命週期
- 載入:找class檔案
- 校驗 :驗證格式,依賴
- 準備 :靜態欄位,方法表
- 解析:符號解析為引用
- 初始化 :構造器,靜態變數賦值,靜態程式碼塊
- 使用
- 解除安裝
類的載入時機
- 當虛擬機器啟動時,初始化使用者指定的主類,就是啟動執行的 main 方法所在的類;
- 當遇到用以新建目標類例項的 new 指令時,初始化 new 指令的目標類,就是 new
一個類的時候要初始化; - 當遇到呼叫靜態方法的指令時,初始化該靜態方法所在的類;
- 當遇到訪問靜態欄位的指令時,初始化該靜態欄位所在的類
- 子類的初始化會觸發父類的初始化
- 如果一個介面定義了 default 方法,那麼直接實現或者間接實現該介面的類的初始化,
會觸發該介面的初始化 - 使用反射 API 對某個類進行反射呼叫時,初始化這個類,其實跟前面一樣,反射呼叫
要麼是已經有例項了,要麼是靜態方法,都需要初始化 - 當初次呼叫 MethodHandle 例項時,初始化該 MethodHandle 指向的方法所在的
類
不會初始化(可能會載入)
- 通過子類引用父類的靜態欄位,只會觸發父類的初始化,而不會觸發子類的初始化
- 定義物件陣列,不會觸發該類的初始化
- 常量在編譯期間會存入呼叫類的常量池中,本質上並沒有直接引用定義常量的類,不會觸發定義常量所在的類
- 通過類名獲取 Class 物件,不會觸發類的初始化,Hello.class 不會讓 Hello 類初始化。
- 通過 Class.forName 載入指定類時,如果指定引數 initialize 為 false 時,也不會觸
發類初始化,其實這個引數是告訴虛擬機器,是否要對類進行初始化。Class.forName
(“jvm.Hello”)預設會載入 Hello 類。 - 通過 ClassLoader 預設的 loadClass 方法,也不會觸發初始化動作(載入了,但是
不初始化)
三類類載入器
從上到下依次是:
- 啟動類載入器(BootstrapClassLoader)
- 擴充套件類載入器(ExtClassLoader)
- 應用類載入器(AppClassLoader)
類載入器特點:
雙親委派、負責依賴、快取載入
載入過程:如一個Hello.class檔案,不考慮自定義載入器,首先會在AppClassLoader中檢查是否已經載入過,如果載入過就不載入了。如果沒有載入過,就會拿到父載入器,那麼父載入器(ExtClassLoader)就會檢查是否載入過,如果沒有,就再往上,讓BootStrapClassLoader檢查是否載入過。
如果還是沒有,因為他的上面已經沒有父載入器了,那麼他就開始自己載入,如果能載入,他就自己載入。不能載入,就下沉到子載入器去載入,一直到最底層,如果沒有類載入器能載入就丟擲異常ClassNotFoundException
新增引用類的幾種方式
- 放到JDK的lib/ext下,或者-Djava.ext.dirs
- java -cp/classpath 或者將class檔案放在當前路徑
- 自定義ClassLoader載入
- 拿到當前執行類的ClassLoader,反射呼叫addUrl方法新增jar或路徑
public class Test1 {
public static void main(String[] args) throws MalformedURLException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
String appurl="file:/d:/logs/";
URLClassLoader classLoader = (URLClassLoader)Test1.class.getClassLoader();
Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
addURL.setAccessible(true);
URL url=new URL(appurl);
addURL.invoke(classLoader,url);
Class.forName("Test2");
}
}
我將Test2.class檔案放在d盤的logs資料夾下。
三、JVM記憶體結構
Jvm整體結構:
可以看到,我們的JVM程式裡面除了堆還有棧、非堆、JVM自身。而我們的作業系統裡還有其他程式。
所以我們設定堆記憶體的時候,不能設定為機器的記憶體大小,如4G的機器千萬不能把-Xms -Xmx 設定為4G,一般設定為機器記憶體的60%-70%。
JVM棧結構:
棧:執行緒棧,也叫Java方法棧,每啟動一個執行緒就會建立一個棧,如果使用了JNI方法,就會分配一個單獨的本地方法棧。執行緒執行過程中,一般會有多個方法組成呼叫關係,如方法A呼叫方法B,每執行到一個方法,就會建立一個棧幀。
所有的原生物件型別(如int,long)和物件引用地址都在棧上儲存。
JVM堆結構:
堆:物件、物件成員以及類定義、靜態變數都在堆上。
什麼是JMM?
Java記憶體模型。明確定義了不同的執行緒之間,通過哪些方式,在什麼時候可以看到其他執行緒儲存在共享變數中的值;以及如何對共享變數進行同步。JMM規範的是執行緒間的互動操作。
從抽象上來看,JMM定義了執行緒和主記憶體之間的抽象關係。
四、JVM啟動引數
JVM啟動引數有如下幾類:
-
以-開頭為標準引數,所有的 JVM 都要實現這些引數,並且向後相容。如:++-server++
-
以-D開頭的,設定系統屬性 如:++-Dfile.encoding=UTF-8++
-
以 -X 開頭為非標準引數, 基本都是傳給 JVM 的,
預設 JVM 實現這些引數的功能,但是並不保證所
有 JVM 實現都滿足,且不保證向後相容。 可以使
用 java -X 命令來檢視當前 JVM 支援的非標準參 如:++-Xmx8g++
數。 -
以 –XX:開頭為非穩定引數, 專門用於控制 JVM
的行為,跟具體的 JVM 實現有關,隨時可能會在
下個版本取消。 -
-XX:+-Flags 形式, +- 是對布林值進行開關 如:++-XX:+UseG1GC++
-
-XX:key=value 形式, 指定某個選項的值 如:++-XX:MaxPermSize=256m++
4.1 系統屬性引數
-Dfile.encoding=UTF-8 -Duser.timezone=GMT+08
或者通過
System.setProperty("a","A100");設定,Linux上還可以通過a=A100 java XXX 設定。
4.2 執行模式
- -server:設定 JVM 使用 server 模式,特點是啟動速度比較慢,但執行時效能和記憶體管理效率
很高,適用於生產環境。在具有 64 位能力的 JDK 環境下將預設啟用該模式,而忽略 -client 參
數。 - -client :JDK1.7 之前在32位的 x86 機器上的預設值是 -client 選項。設定 JVM 使用 client 模
式,特點是啟動速度比較快,但執行時效能和記憶體管理效率不高,通常用於客戶端應用程式或
者 PC 應用開發和除錯。此外,我們知道 JVM 載入位元組碼後,可以解釋執行,也可以編譯成本
地程式碼再執行,所以可以配置 JVM 對位元組碼的處理模式: - -Xint:在解釋模式(interpreted mode)下執行,-Xint 標記會強制 JVM 解釋執行所有的位元組
碼,這當然會降低執行速度,通常低10倍或更多。 - -Xcomp:-Xcomp 引數與-Xint 正好相反,JVM 在第一次使用時會把所有的位元組碼編譯成本地
程式碼,從而帶來最大程度的優化。【注意預熱】 - -Xmixed:-Xmixed 是混合模式,將解釋模式和編譯模式進行混合使用,有 JVM 自己決定,這
是 JVM 的預設模式,也是推薦模式。 我們使用 java -version 可以看到 mixed mode 等資訊。
4.3 堆記憶體
-Xmx, 指定最大堆記憶體。 如 -Xmx4g. 這只是限制了 Heap 部分的最大值為4g。
這個記憶體不包括棧記憶體,也不包括堆外使用的記憶體。
-Xms, 指定堆記憶體空間的初始大小。 如 -Xms4g。 而且指定的記憶體大小,並
不是作業系統實際分配的初始值,而是GC先規劃好,用到才分配。 專用伺服器上需要保持 –Xms 和 –Xmx 一致,否則應用剛啟動可能就有好幾個 FullGC。
當兩者配置不一致時,堆記憶體擴容可能會導致效能抖動。
-Xmn, 等價於 -XX:NewSize,使用 G1 垃圾收集器 不應該設定該選項,在其他的某些業務場景下可以設定。官方建議設定為 -Xmx 的 1/2 ~ 1/4.
-XX:MaxPermSize=size, 這是 JDK1.7 之前使用的。Java8 預設允許的Meta空間無限大,此引數無效。
-XX:MaxMetaspaceSize=size, Java8 預設不限制 Meta 空間, 一般不允許設定該選項。
-XX:MaxDirectMemorySize=size,系統可以使用的最大堆外記憶體,這個引數跟 -Dsun.nio.MaxDirectMemorySize 效果相同。
-Xss, 設定每個執行緒棧的位元組數。 例如 -Xss1m指定執行緒棧為1MB,與-XX:ThreadStackSize=1m 等價
4.4 GC相關
-XX:+UseG1GC:使用 G1 垃圾回收器
-XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器
-XX:+UseSerialGC:使用序列垃圾回收器
-XX:+UseParallelGC:使用並行垃圾回收器
// Java 11+
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
// Java 12+
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
4.5 分析診斷
-XX:+-HeapDumpOnOutOfMemoryError 選項, 當 OutOfMemoryError 產生,即記憶體溢位(堆記憶體或持久代)時自動 Dump 堆記憶體。
示例用法: java -XX:+HeapDumpOnOutOfMemoryError -Xmx256m ConsumeHeap
-XX:HeapDumpPath 選項, 與 HeapDumpOnOutOfMemoryError 搭配使用, 指定記憶體溢位時 Dump 檔案的目錄。
如果沒有指定則預設為啟動 Java 程式的工作目錄。
示例用法: java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/ ConsumeHeap
自動 Dump 的 hprof 檔案會儲存到 /usr/local/ 目錄下。
-XX:OnError 選項, 發生致命錯誤時(fatal error)執行的指令碼。
例如, 寫一個指令碼來記錄出錯時間, 執行一些命令, 或者 curl 一下某個線上報警的 url.
示例用法:java -XX:OnError="gdb - %p" MyApp
可以發現有一個 %p 的格式化字串,表示程式 PID。
-XX:OnOutOfMemoryError 選項, 丟擲 OutOfMemoryError 錯誤時執行的指令碼。
-XX:ErrorFile=filename 選項, 致命錯誤的日誌檔名,絕對路徑或者相對路徑。
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1506,遠端除錯
4.6 JavaAgent
Agent 是 JVM 中的一項黑科技, 可以通過無侵入方式來做很多事情,比如注入 AOP 程式碼,執行統
計等等,許可權非常大。這裡簡單介紹一下配置選項,詳細功能需要專門來講。
設定 agent 的語法如下:
-agentlib:libname[=options] 啟用 native 方式的 agent, 參考 LD_LIBRARY_PATH 路徑。
-agentpath:pathname[=options] 啟用 native 方式的 agent。
-javaagent:jarpath[=options] 啟用外部的 agent 庫, 比如 pinpoint.jar 等等。
-Xnoagent 則是禁用所有 agent。
以下示例開啟CPU使用時間抽樣分析:JAVA_OPTS="-agentlib:hprof=cpu=samples,file=cpu.samples.log