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/4 ~ 1/2.
-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