和虛擬機談戀愛的那些事兒

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問題呢?

  • 更換虛擬機垃圾回收策略;
  • 檢查代碼中是不是有過多的強引用;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章