java程序員離不開虛擬機,一個好的程序必須是和虛擬機配合很好的程序,程序寫的再牛逼,和虛擬機的特性不匹配,結果是弄巧成拙。你必須和虛擬機談一場戀愛,要想談好戀愛,你就得了解虛擬機的結構、原理、特性。
一、虛擬機基礎知識
1、虛擬機的分類
系統虛擬機和程序虛擬機;大名鼎鼎的Visual Box和VMware就是系統虛擬機,而JVM,也就是java虛擬機其實式程序虛擬機。
2、java虛擬機不止一種
Classic、Exact VM、HotSopt等都是JVM
3、查看自己電腦上虛擬機的默認參數
4、虛擬機的工作模式
Client和Server二選一。
默認情況下虛擬機會根據計算機操作系統的情況自動選擇運行模式。
與Client相比,Server模式啓動更慢,因爲Server模式會嘗試收集更多的系統性能信息,使用更復雜的優化算法對程序進行優化。
5、鎖在虛擬機中的實現和優化
- 偏向鎖
- 輕量級鎖
- 膨脹鎖
- 自旋鎖
- 消除鎖
在瞭解虛擬機內部對鎖的支持之前,我們先了解一個概念:對象頭。
什麼是對象頭?
在Java虛擬機的實現中每一個對象都有一個對象頭,用來用來保存對象的系統信息。對象頭中有一個Mark Word的部分,它是實現鎖的關鍵。
以32位系統爲例,普通對象的對象頭如下所示:
hash:25 ---------------->| age:4 biased_lock:1 lock:2
它表示在Mark Word中有25位比特位表示對象的哈希值,4位表示對象的年齡,1位表示是否位偏向鎖,2位表示鎖的信息。
1 | 01 | 可偏向/未鎖定 |
0 | 00 | 對象處於輕量級鎖定 |
0 | 10 | 對象處於重量級鎖定 |
0 | 01 | 普通的未鎖定狀態 |
偏向鎖的核心思想是:如果程序沒有競爭,則取消已經取得鎖的線程同步操作。當某一鎖被線程獲取後,便進入偏向模式,當該線程再請求這個鎖是,無需再進行相關的同步操作,從而節省了操作時間。如果在此之間其他線程進行鎖的請求,則鎖退出偏向模式。
舉個例子:Boy AA(線程AA)追求一個Gril BB(資源),第一次追求需要寫一份情書(鎖),Gril BB,同意後Gril BB在心裏將Boy AA設置成男朋友,並設置成戀愛狀態(偏向模式)。接下來Boy AA就可以使用這個Gril BB的內部資源(牽手、接吻等)。下次再牽手啥的接不用再寫情書了。
但是如果Gril BB被另一個Boy CC給追求了,而且Gril BB同意了。那麼她就退出了戀愛狀態(偏向模式),因爲她處於劈腿模式。
當鎖對象處於偏向模式時,對象頭會被記錄獲得鎖的線程。這樣,當該線程再次嘗試獲得鎖時,通過Mark Word的線程信息就可以判斷當前線程是否持有偏向鎖。
偏向鎖在少競爭的情況下,對系統吸能有一定的幫助。但是在競爭激烈的場景下,可以禁止使用偏向鎖。
禁止偏向鎖的設置方式:-XX:-UseBiasedLocking
二、分析java堆
本小節包括的內容:
- 常見的內存溢出原因及解決思路
- 有關java.lang.String的探討
- 使用Visual VM分析堆
- 使用MAT分析堆
常見的內存溢出原因及解決思路
1、堆溢出
/**
* @program: data-structure
* @description: 堆溢出
* @author: Miller.FAN
* @create: 2019-12-04 20:38
**/
public class HeapOOM {
public static void main(String[] args) {
ArrayList<byte[]> list = new ArrayList<>();
for (int i = 0; i<100000; i++) {
list.add(new byte[1024*1024]);
}
}
}
堆溢出:com.miller.datastructure.gcc.HeapOOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.miller.datastructure.gcc.HeapOOM.main(HeapOOM.java:15)
2、直接內存溢出
for (int j =0; j< 10000; j++) {
ByteBuffer.allocateDirect(1024*1024*1024);
System.out.println(j);
//System.gc();
}
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
3、過多線程導致OOM
由於線程的棧空間也是在堆外分配的,因此和直接內存非常相似,如果想讓系統支持更多的線程,那麼應該使用一個較小的堆空間。
另一個方法是使用參數:-Xss128k 指定線程的棧空間,如果一個線程的棧空間分配比較小,那麼理應容納更多的線程。前提是保證單個線程分配的棧空間是足夠的。
4、永久區溢出
永久區是存放類元數據的區域。如果一個系統定義了太多的類型,那麼永久區是有可能溢出的。JDK1.8中永久區被一塊被稱爲元數據的區域代替,但是功能是類似的。
要解決元數據區溢出的問題,可以從以下三方面入手:
- 增加MaxPermSize的值;
- 減少系統需要的類的數量;
- 使用ClassLoader合理地裝載各個類,並定期進行回收。
5、GC效率低下引起的OOM
此時,虛擬機會拋出的異常: GC overhead limit exceeded
如何解決GC效率低下造成的OOM問題呢?
- 更換虛擬機垃圾回收策略;
- 檢查代碼中是不是有過多的強引用;