JVM結構
Java8 JVM內存結構
基本結構與之前類似,只是Java8取消了之前的“永久代”,取而代之的是“元空間”——Metaspace,兩者本質是一樣的。“永久代”使用的是JVM的堆內存,而“元空間”是直接使用的本機物理內存。
簡單來說就是棧管運行,而堆管存儲
GC Roots
如果判斷一個對象可以被回收?
引用計數算法
維護一個計數器,如果有對該對象的引用,計數器+1,反之-1。無法解決循環引用的問題。
可達性分析算法
從一組名爲“GC Roots”的根節點對象出發,向下遍歷。那些沒有被遍歷到、與GC Roots形成通路的對象,會被標記爲“回收”。
哪些對象可以作爲GC Roots?
- 虛擬機棧(棧幀中的局部變量)中引用的對象。
- 本地方法棧(native)中引用的對象。
- 方法區中常量引用的對象。
- 方法區中類靜態屬性引用的對象。
JVM參數
JVM 三種類型參數
標配參數
比如-version
、-help
、-showversion
等,幾乎不會改變。
X參數
用得不多,比如-Xint
,解釋執行模式;-Xcomp
,編譯模式;-Xmixed
,開啓混合模式(默認)。
XX參數
重要,用於JVM調優。
JVM XX參數
布爾類型
公式:-XX:+某個屬性
、-XX:-某個屬性
,開啓或關閉某個功能。
比如-XX:+PrintGCDetails
,開啓GC詳細信息。
KV鍵值類型
公式:-XX:屬性key=值value
。比如-XX:Metaspace=128m
、
-XX:MaxTenuringThreshold=15
。
JVM Xms/Xmx參數
-Xms
和-Xmx
十分常見,用於設置初始堆大小和最大堆大小。第一眼看上去,既不像X參數,也不像XX參數。實際上-Xms
等價於-XX:InitialHeapSize
,-Xmx
等價於-XX:MaxHeapSize
。所以-Xms
和-Xmx
屬於XX參數。
JVM 查看參數
查看某個參數
使用jps -l
配合jinfo -flag JVM參數 pid
。先用jsp -l
查看java進程,選擇某個進程號。
jinfo -flag PrintGCDetails 18052
可以查看18052 Java進程的PrintGCDetails
參數信息。
公式:-XX:+某個屬性
、-XX:-某個屬性
,開啓或關閉某個功能。
比如-XX:+PrintGCDetails
,開啓GC詳細信息。
查看所有參數
使用jps -l
配合jinfo -flags pid
可以查看所有參數。
也可以使用java -XX:+PrintFlagsInitial
查看修改後的參數
使用java -XX:PrintFlagsFinal
可以查看修改後的參數,與上面類似。只是修改過後是:=
而不是=
。
查看常見參數
如果不想查看所有參數,可以用-XX:+PrintCommandLineFlags
查看常用參數。
JVM 常用參數
-Xmx/-Xms
最大和初始堆大小。最大默認爲物理內存的1/4,初始默認爲物理內存的1/64。
-Xss
等價於-XX:ThresholdStackSize
。用於設置單個棧的大小,系統默認值是0,不代表棧大小爲0。而是根據操作系統的不同,有不同的值。比如64位的Linux系統是1024K,而Windows系統依賴於虛擬內存。
-Xmn
新生代大小,一般不調。
-XX:MetaspaceSize
設置元空間大小。
-XX:+PrintGCDetails
輸出GC收集信息,包含GC
和Full GC
信息。
-XX:SurvivorRatio
新生代中,Eden
區和兩個Survivor
區的比例,默認是8:1:1
。通過-XX:SurvivorRatio=4
改成4:1:1
-XX:NewRatio
老生代和新年代的比列,默認是2,即老年代佔2,新生代佔1。如果改成-XX:NewRatio=4
,則老年代佔4,新生代佔1。
-XX:MaxTenuringThreshold
新生代設置進入老年代的時間,默認是新生代逃過15次GC後,進入老年代。如果改成0,那麼對象不會在新生代分配,直接進入老年代。
四大引用
強引用
使用new
方法創造出來的對象,默認都是強引用。GC的時候,就算內存不夠,拋出OutOfMemoryError
也不會回收對象,死了也不回收。我們一般創建對象都是強引用
軟引用
需要用Object.Reference.SoftReference
來顯示創建。如果內存夠,GC的時候不回收。內存不夠,則回收。常用於內存敏感的應用,比如高速緩存。
package jvm;
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
public static void main(String[] args) {
softRef_Memory_Enough();
System.out.println("Not Enough");
softRef_Memory_NotEnough();
}
private static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
System.out.println("===========");
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
private static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
System.out.println("===========");
o1 = null;
System.gc();
try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
}
弱引用
需要用Object.Reference.WeakReference
來顯示創建。無論內存夠不夠,GC的時候都回收,也可以用在高速緩存上。
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println("==========");
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(weakReference.get());
}
WeakHashMap
傳統的HashMap
就算key==null
了,也不會回收鍵值對。但是如果是WeakHashMap
,一旦內存不夠用時,且key==null
時,會回收這個鍵值對。
public static void main(String[] args) {
byte[] bytes = new byte[30 * 1024 * 1024];
myHashMap();
System.out.println("===============");
myWeakHashMap();
}
private static void myHashMap() {
HashMap<Integer, String> map = new HashMap<>();
Integer key = 1;
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc();
System.out.println(map + "\t" + map.size());
}
private static void myWeakHashMap() {
WeakHashMap<Integer, String> map = new WeakHashMap<>();
Integer key = 2;
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc();
System.out.println(map + "\t" + map.size());
}
虛引用
軟應用和弱引用可以通過get()
方法獲得對象,但是虛引用不行。虛引形同虛設,在任何時候都可能被GC,不能單獨使用,必須配合引用隊列(ReferenceQueue)來使用。設置虛引用的唯一目的,就是在這個對象被回收時,收到一個通知以便進行後續操作,有點像Spring
的後置通知。
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference phantomReference = new PhantomReference(o1, referenceQueue);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
System.out.println("===========");
o1 = null;
System.gc();
Thread.sleep(500);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
}
引用隊列
弱引用、虛引用被回收後,會被放到引用隊列裏面,通過poll
方法可以得到。關於引用隊列和弱、虛引用的配合使用,