jvm、gc、作業系統等基礎知識總結

forrunning發表於2019-04-09

作業系統: 1,32位機器和64位機器 一、32位和64位是什麼意思 1、32位和64位表示CPU一次能處理的最大位數; 2、32位CPU只能安裝32位系統,64位CPU可以安裝32位和64位系統; 3、如今市面上的CPU大多數為64位,怎麼看CPU是32位還是64位。 二、定址能力 1、32位系統的最大定址空間是2的32次方=4294967296(bit)= 4(GB)左右,也就是說在32位機器整個作業系統中,所有應用程式加起來最多隻能使用4g記憶體。 2、64位系統的最大定址空間為2的64次方=4294967296(bit)的32次方,數值大於1億GB; 3、也就是說32位系統的處理器最大隻支援到4G記憶體,而64位系統最大支援的記憶體高達億位數,實際使用過程中大多數的電腦32位系統最多識別3.5GB記憶體,64位系統最多識別128GB記憶體。

那麼32位系統如何實現所謂超過4G記憶體破解,原理很簡單,現在CPU基本都是64位處理器,也就是硬體是沒有4GB的識別問題,破解就是應用64位系統定址原理,來開啟32位系統對超過4G記憶體識別限制。而將這些多出來記憶體則作為RAMDISK來使用,就是快取盤,在WINDOWS 下軟體執行都會產生臨時檔案,那麼他就是將這些軟體產品臨時檔案都搬到RANDISK上來,而不寫入磁碟。在某個角度上來的確可以提高系統執行速度。但並不是真正系統和軟體使用64位系統和軟體定址方式,仍然是32位的定址。如果將這個破解應用在純32位CPU上,你再試試看他能否能開啟所謂超4G記憶體的破解。

資料在儲存器(RAM)中存放是有規律的 ,CPU在運算的時候需要把資料提取出來就需要知道資料存放在哪裡 ,這時候就需要挨家挨戶的找,這就叫做定址,但如果地址太多超出了CPU的能力範圍,CPU就無法找到資料了。 CPU最大能查詢多大範圍的地址叫做定址能力 ,CPU的定址能力以位元組為單位,如32位定址的CPU可以定址2的32次方大小的地址也就是4G,這也是為什麼32位的CPU最大能搭配4G記憶體的原因,再多的話CPU就找不到了。

通常人們認為,記憶體容量越大,處理資料的能力也就越強,但記憶體容量不可能無限的大,它要受到系統結構、硬體設計、製造成本等多方面因素的制約,一個最直接的因素取決於系統的地址匯流排的地址暫存器的寬度(位數)。  計算機的尋找範圍由匯流排寬度(處理器的地址匯流排的位數)決定的,也可以理解為cpu暫存器位數,這二者一般是匹配的。

Intel公司早期的CPU產品的地址匯流排和地址暫存器的寬度為20位,即CPU的定址能力為2^20=10241024位元組=1024K位元組=1M位元組;286的地址匯流排和地址暫存器的寬度為24位,CPU的定址能力為2^24=1024410244B=410244KB=16M;386及386以上的地址匯流排和地址暫存器的寬度為32位,CPU的定址能力為2^32=4096M位元組=4G位元組。 也就是說,如果機器的CPU過早,即使有很大的記憶體也不能得到利用,而對於現在的PⅡ級的CPU,其定址能力已遠遠超過目前的記憶體容量。由此推出:地址匯流排為N位(N通常都是8的整數倍;也說N根資料匯流排)的CPU定址範圍是2的N次方位元組,即2^N(B)

如果真正想有效讓系統分配超過4G的記憶體,建議還是使用64位系統,只有64位系統才能原生支援超過4G記憶體的定址。

參考:blog.csdn.net/fengyuwuzu0…

2,java一些位元組位問題 一箇中文字元不同編碼使用的位元組數: UTF-8編碼長度:4 GBK編碼長度:3 GB2312編碼長度:3

一個英文字元不同編碼使用的位元組數: UTF-8編碼長度:1 GBK編碼長度:1 GB2312編碼長度:1

1KB=1024B 1MB=1024KB 1GB=1024MB

對於2億條評論內容佔的空間大小,評價一條評價50字左右,一個漢字3個位元組,2億條評論,1GB=102410241024,需要的空間大於3gb

3,Java物件的表示模型和執行時記憶體表示

<1>Hotspot主要是用C++寫的,所以它定義的Java物件表示模型也是基於C++實現的。 Java物件的表示模型叫做“OOP-Klass”二分模型,包括兩部分:

  1. OOP,即Ordinary Object Point,普通物件指標。說實話這個名稱挺難理解。說白了其實就是表示物件的例項資訊
  2. Klass,即Java類的C++對等體,用來描述Java類,包含了後設資料和方法資訊等 <2>一個Java物件就包括兩部分,資料和方法,分別對應到OOP和Klass。最簡單的理解就是如果讓你自己用Java語言來開發一套新的語言,你如何來表示這個新的語言的物件呢。肯定也是類似的思路,一個模組是用Java類來實現表示資料的部分,一個模組是用Java類實現表示方法和後設資料的部分。 <3>JVM執行時載入一個Class時,會在JVM內部建立一個instanceKlass物件,表示這個類的執行時後設資料。建立一個這個Class的Java物件時,會在JVM內部相應的建立一個instanceOop來表示這個Java物件。熟悉JVM的同學可以明白,instanceKlass物件放在了方法區,instanceOop放在了堆,instanceOop的引用放在了JVM棧 <4>在堆中建立的Java物件實際只包含資料資訊,它包含三部分:
  3. 物件頭,也叫Mark Word
  4. 後設資料指標,可以理解為類物件指標,指向方法區的instanceKlass例項
  5. 例項資料 如果是資料物件的話,還多了一個部分,就是陣列長度 物件頭主要儲存物件執行時記錄資訊,如hashcode, GC分代年齡,鎖狀態標誌,偏向執行緒ID,偏向時間戳等。物件頭的長度和JVM的字長一致,比如32位JVM的物件頭是32位,64位JVM的物件頭是64位。 這裡可以看到,所謂的給一個物件加鎖,其實就是設定了物件頭某些位。當其他執行緒看到這個物件的狀態是加鎖狀態後,就等待釋放鎖。 在方法區的instanceKlass物件相當於Class載入後建立的執行時物件,它包含了執行時常量池,欄位,方法等後設資料,當呼叫一個物件的方法時,如上面的圖所示,實際定位到了方法區的instanceKlass物件的方法後設資料。

參考:blog.csdn.net/iter_zc/art…

4,Java物件大小、物件記憶體佈局及鎖狀態變化 Mark Word:儲存物件執行時記錄資訊,佔用記憶體大小與機器位數一樣,即32位機佔4位元組,64位機佔8位元組 後設資料指標:指向描述型別的Klass物件(Java類的C++對等體)的指標,Klass物件包含了例項物件所屬型別的後設資料,因此該欄位被稱為後設資料指標,JVM在執行時將頻繁使用這個指標定位到位於方法區內的型別資訊。這個資料的大小稍後說 陣列長度:陣列物件特有,一個指向int型的引用型別,用於描述陣列長度,這個資料的大小和後設資料指標大小相同,同樣稍後說 例項資料:例項資料就是8大基本資料型別byte、short、int、long、float、double、char、boolean(物件型別也是由這8大基本資料型別複合而成),每種資料型別佔多少位元組就不一一例舉了 填充:補齊位置使得整體為8位元組的整數倍,HotSpot的對齊方式為8位元組對齊,即一個物件必須為8位元組的整數倍,因此如果最後前面的資料大小為17則填充7,前面的資料大小為18則填充6,以此類推

說說後設資料指標的大小。後設資料指標是一個引用型別,因此正常來說64位機後設資料指標應當為8位元組,32位機後設資料指標應當為4位元組,但是HotSpot中有一項優化是對後設資料型別指標進行壓縮儲存,使用JVM引數: -XX:+UseCompressedOops開啟壓縮 -XX:-UseCompressedOops關閉壓縮 HotSpot預設是前者,即開啟後設資料指標壓縮,當開啟壓縮的時候,64位機上的後設資料指標將佔據4個位元組的大小。換句話說就是當開啟壓縮的時候,64位機上的引用將佔據4個位元組,否則是正常的8位元組。

舉例指標壓縮 首先是Object物件的大小: 開啟指標壓縮時,8位元組Mark Word + 4位元組後設資料指標 = 12位元組,由於12位元組不是8的倍數,因此填充4位元組,物件Object佔據16位元組記憶體 關閉指標壓縮時,8位元組Mark Word + 8位元組後設資料指標 = 16位元組,由於16位元組正好是8的倍數,因此不需要填充位元組,物件Object佔據16位元組記憶體

接著是字元'a'的大小: 開啟指標壓縮時,8位元組Mark Word + 4位元組後設資料指標 + 1位元組char = 13位元組,由於13位元組不是8的倍數,因此填充3位元組,字元'a'佔據16位元組記憶體 關閉指標壓縮時,8位元組Mark Word + 8位元組後設資料指標 + 1位元組char = 17位元組,由於17位元組不是8的倍數,因此填充7位元組,字元'a'佔據24位元組記憶體

接著是整型1的大小: 開啟指標壓縮時,8位元組Mark Word + 4位元組後設資料指標 + 4位元組int = 16位元組,由於16位元組正好是8的倍數,因此不需要填充位元組,整型1佔據16位元組記憶體 關閉指標壓縮時,8位元組Mark Word + 8位元組後設資料指標 + 4位元組int = 20位元組,由於20位元組正好是8的倍數,因此填充4位元組,整型1佔據24位元組記憶體

接著是字串"aaaaa"的大小,所有靜態欄位不需要管,只關注例項欄位,String物件中例項欄位有"char value[]"與"int hash",由此可知: 開啟指標壓縮時,8位元組Mark Word + 4位元組後設資料指標 + 4位元組引用 + 4位元組int = 20位元組,由於20位元組不是8的倍數,因此填充4位元組,字串"aaaaa"佔據24位元組記憶體 關閉指標壓縮時,8位元組Mark Word + 8位元組後設資料指標 + 8位元組引用 + 4位元組int = 28位元組,由於28位元組不是8的倍數,因此填充4位元組,字串"aaaaa"佔據32位元組記憶體

最後是長度為1的char型陣列的大小: 開啟指標壓縮時,8位元組的Mark Word + 4位元組的後設資料指標 + 4位元組的陣列大小引用 + 1位元組char = 17位元組,由於17位元組不是8的倍數,因此填充7位元組,長度為1的char型陣列佔據24位元組記憶體 關閉指標壓縮時,8位元組的Mark Word + 8位元組的後設資料指標 + 8位元組的陣列大小引用 + 1位元組char = 25位元組,由於25位元組不是8的倍數,因此填充7位元組,長度為1的char型陣列佔據32位元組記憶體

備註:開啟指標壓縮技術的前提條件:當jvm是64位且記憶體小於32G會預設開啟指標壓縮技術,那麼按理說,4位元組的最大定址空間應該只有2的32次方,即4GB記憶體堆記憶體,明顯不符合使用了,實際上不是這樣的, 解釋:並非如此, 由於物件是8位元組對齊的, 因此物件起始地址最低三位總是0, 因此可以儲存時可以右移3bit, 高位空出來的3bit可以表示更高的數值, 實際上, 可以使用指標壓縮的maxHeapSize是4G * 8 = 32G.

參考:blog.csdn.net/lqp276/arti…

4,java如何計算一個物件的大小。 blog.csdn.net/bobpauline/… www.jianshu.com/p/9d729c9c9… blog.csdn.net/iter_zc/art…

public class ObjectSizeServiceTest { public static class Person { //private String name = "zhangsan"; private int age = 10; // private long age2 = 100; private double age3 = 20.3;

}

public static void main(String[] args) {
    //藉助lucene的RamUsageEstimator,內部是基於Unsafe實現,實現了遞迴處理獲取引用物件的大小
    Object object = new Object();
    Integer integerValue = 100;
    //System.out.println(RamUsageEstimator.sizeOf(integerValue));
    //System.out.println(RamUsageEstimator.sizeOf(object));
    Person person = new Person();
    System.out.println(RamUsageEstimator.sizeOf(person));
    System.out.println(getSizeByUnsafe(person));

}

/**
 * 物件頭部的大小
 */
private static final int OBJECT_HEADER_SIZE = 8;
/**
 * 物件佔用記憶體的最小值
 */
private static final int MINIMUM_OBJECT_SIZE = 8;
/**
 * 物件按多少位元組的粒度進行對齊
 */
private static final int OBJECT_ALIGNMENT = 8;

// 獲得Unsafe例項
//暫時不考慮物件的相互引用,還有超類,字串,陣列
public static long getSizeByUnsafe(Object object) {
    Unsafe unsafe;
    try {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        unsafe = (Unsafe) unsafeField.get(null);
    } catch (Throwable t) {
        unsafe = null;
    }
    Class kClass = object.getClass();
    long lastFieldOffset = -1;
    for (Field f : kClass.getDeclaredFields()) {
        if (!Modifier.isStatic(f.getModifiers())) {
            lastFieldOffset = Math.max(lastFieldOffset, unsafe.objectFieldOffset(f));
        }
    }
    if (lastFieldOffset > 0) {
        lastFieldOffset += 1;
        if ((lastFieldOffset % OBJECT_ALIGNMENT) != 0) {
            lastFieldOffset += OBJECT_ALIGNMENT - (lastFieldOffset % OBJECT_ALIGNMENT);
        }
        return Math.max(MINIMUM_OBJECT_SIZE, lastFieldOffset);
    }
    //該物件沒有任何屬性
    long size = OBJECT_HEADER_SIZE;
    if ((size % OBJECT_ALIGNMENT) != 0) {
        size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
    }
    return Math.max(MINIMUM_OBJECT_SIZE, size);
}
複製程式碼

} 5,垃圾回收器 從執行緒執行情況分類有三種 序列回收,Serial回收器,單執行緒回收,全程stw; 並行回收,名稱以Parallel開頭的回收器,多執行緒回收,全程stw; 併發回收,cms與G1,多執行緒分階段回收,只有某階段會stw;

blog.csdn.net/zqz_zqz/art…

6,jvm記憶體分割槽 JVM的記憶體劃分中,有部分割槽域是執行緒私有的,有部分是屬於整個JVM程式;有些區域會丟擲OOM異常,有些則不會,瞭解JVM的記憶體區域劃分以及特徵,是定位線上記憶體問題的基礎。那麼JVM記憶體區域是怎麼劃分的呢?

第一,程式計數器(Program Counter Register),在JVM規範中,每個執行緒都有自己的程式計數器(所以說這塊空間是執行緒安全的)。這是一塊比較小的記憶體空間,儲存當前執行緒正在執行的Java方法的JVM指令地址,即位元組碼的行號。如果正在執行Native方法,則這個計數器為空。該記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OOM情況的記憶體區域。(程式計數器是一個記錄著當前執行緒所執行的位元組碼的行號指示器,JAVA程式碼編譯後的位元組碼在未經過JIT(實時編譯器)編譯前,其執行方式是通過“位元組碼直譯器”進行解釋執行。簡單的工作原理為直譯器讀取裝載入記憶體的位元組碼,按照順序讀取位元組碼指令。讀取一個指令後,將該指令“翻譯”成固定的操作,並根據這些操作進行分支、迴圈、跳轉等流程。 )

第二,Java虛擬機器棧(Java Virtal Machine Stack),同樣也是屬於執行緒私有區域,每個執行緒在建立的時候都會建立一個虛擬機器棧,生命週期與執行緒一致,執行緒退出時,執行緒的虛擬機器棧也回收。虛擬機器棧內部保持一個個的棧幀,每次方法呼叫都會進行壓棧,JVM對棧幀的操作只有出棧和壓棧兩種,方法呼叫結束時會進行出棧操作。

該區域儲存著區域性變數表,編譯時期可知的各種基本型別資料、物件引用、方法出口等資訊。

第三,本地方法棧(Native Method Stack)與虛擬機器棧類似,本地方法棧是在呼叫本地方法時使用的棧,每個執行緒都有一個本地方法棧。

第四,堆(Heap),幾乎所有建立的Java物件例項,都是被直接分配到堆上的。堆被所有的執行緒所共享,在堆上的區域,會被垃圾回收器做進一步劃分,例如新生代、老年代的劃分。Java虛擬機器在啟動的時候,可以使用“Xmx”之類的引數指定堆區域的大小。

第五,方法區(Method Area)。方法區與堆一樣,也是所有的執行緒所共享,儲存被虛擬機器載入的元(Meta)資料,包括類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。這裡需要注意的是執行時常量池也在方法區中。根據Java虛擬機器規範的規定,當方法區無法滿足記憶體分配需求時,將丟擲OutOfMemoryError異常。由於早期HotSpot JVM的實現,將CG分代收集擴充到了方法區,因此很多人會將方法區稱為永久代。Oracle JDK8中已永久代移除永久代,同時增加了後設資料區(Metaspace)。

第六,執行時常量池(Run-Time Constant Pool),這是方法區的一部分,受到方法區記憶體的限制,當常量池無法再申請到記憶體時,會丟擲OutOfMemoryError異常。 在Class檔案中,除了有類的版本、方法、欄位、介面等描述資訊外,還有一項資訊是常量池。每個Class檔案的頭四個位元組稱為Magic Number,它的作用是確定這是否是一個可以被虛擬機器接受的檔案;接著的四個位元組儲存的是Class檔案的版本號。緊挨著版本號之後的,就是常量池入口了。常量池主要存放兩大類常量:

字面量(Literal),如文字字串、final常量值 符號引用,存放了與編譯相關的一些常量,因為Java不像C++那樣有連線的過程,因此欄位方法這些符號引用在執行期就需要進行轉換,以便得到真正的記憶體入口地址。 class檔案中的常量池,也稱為靜態常量池,JVM虛擬機器完成類裝載操作後,會把靜態常量池載入到記憶體中,存放在執行時常量池。 第七,直接記憶體(Direct Memory),直接記憶體並不屬於Java規範規定的屬於Java虛擬機器執行時資料區的一部分。Java的NIO可以使用Native方法直接在java堆外分配記憶體,使用DirectByteBuffer物件作為這個堆外記憶體的引用。

OOM可能發生在哪些區域上? 根據javadoc的描述,OOM是指JVM的記憶體不夠用了,同時垃圾收集器也無法提供更多的記憶體。從描述中可以看出,在JVM丟擲OutOfMemoryError之前,垃圾收集器一般會出馬先嚐試回收記憶體。 從上面分析的Java資料區來看,除了程式計數器不會發生OOM外,哪些區域會發生OOM的情況呢?

第一,堆記憶體。堆記憶體不足是最常見的傳送OOM的原因之一,如果在堆中沒有記憶體完成物件例項的分配,並且堆無法再擴充套件時,將丟擲OutOfMemoryError異常,丟擲的錯誤資訊是“java.lang.OutOfMemoryError:Java heap space”。當前主流的JVM可以通過-Xmx和-Xms來控制堆記憶體的大小,發生堆上OOM的可能是存在記憶體洩露,也可能是堆大小分配不合理。

第二,Java虛擬機器棧和本地方法棧,這兩個區域的區別不過是虛擬機器棧為虛擬機器執行Java方法服務,而本地方法棧則為虛擬機器使用到的Native方法服務,在記憶體分配異常上是相同的。在JVM規範中,對Java虛擬機器棧規定了兩種異常:1.如果執行緒請求的棧大於所分配的棧大小,則丟擲StackOverFlowError錯誤,比如進行了一個不會停止的遞迴呼叫;2. 如果虛擬機器棧是可以動態擴充的,擴充時無法申請到足夠的記憶體,則丟擲OutOfMemoryError錯誤。

第三,直接記憶體。直接記憶體雖然不是虛擬機器執行時資料區的一部分,但既然是記憶體,就會受到實體記憶體的限制。在JDK1.4中引入的NIO使用Native函式庫在堆外記憶體上直接分配記憶體,但直接記憶體不足時,也會導致OOM。

第四,方法區。隨著Metaspace後設資料區的引入,方法區的OOM錯誤資訊也變成了“java.lang.OutOfMemoryError:Metaspace”。對於舊版本的Oracle JDK,由於永久代的大小有限,而JVM對永久代的垃圾回收並不積極,如果往永久代不斷寫入資料,例如String.Intern()的呼叫,在永久代佔用太多空間導致記憶體不足,也會出現OOM的問題,對應的錯誤資訊為“java.lang.OutOfMemoryError:PermGen space”

記憶體區域 是否執行緒私有 是否可能發生OOM 程式計數器 是 否 虛擬機器棧 是 是 本地方法棧 是 是 方法區 否 是 直接記憶體 否 是 堆 否 是 參考文章:www.cnblogs.com/QG-whz/p/96…

7,jvm調優 JVM中最大堆大小有三方面限制:相關作業系統的資料模型(32-bt還是64-bit)限制;系統的可用虛擬記憶體限制;系統的可用實體記憶體限制。32位系統下,一般限制在1.5G~2G;64為作業系統對記憶體無限制。在Windows Server 2003 系統,3.5G實體記憶體,JDK5.0下測試,最大可設定為1478m,linux系統自身一般佔用1g的記憶體

<1>預設的新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過引數 –XX:NewRatio 來指定) 新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ),被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。預設,Edem : from : to = 8 :1 : 1 ( 可以通過引數–XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。

<2>舉例配置: java -Xmx3550m -Xms3550m -Xmn2g –Xss128k

-Xmx3550m:設定JVM最大可用記憶體為3550M。 -Xms3550m:設定JVM促使記憶體為3550m。此值可以設定與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配記憶體。 -Xmn2g:設定年輕代大小為2G。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代後,將會減小年老代大小。此值對系統效能影響較大,Sun官方推薦配置為整個堆的3/8。 -Xss128k:設定每個執行緒的堆疊大小。JDK5.0以後每個執行緒堆疊大小為1M,以前每個執行緒堆疊大小為256K。更具應用的執行緒所需記憶體大小進行調整。在相同實體記憶體下,減小這個值能生成更多的執行緒。但是作業系統對一個程式內的執行緒數還是有限制的,不能無限生成,經驗值在3000~5000左右。

備註: -Xss規定了每個執行緒堆疊的大小。一般情況下256K是足夠了。影響了此程式中併發執行緒數大小。 -Xms初始的Heap的大小。 -Xmx最大Heap的大小。 永久代 PermSize和MaxPermSize設定為老年代存活物件的1.2-1.5倍(比如-XX:MaxPermSize=16m:設定持久代大小為16m,這個空間比較小)。 在很多情況下,-Xms和-Xmx設定成一樣的。這麼設定,是因為當Heap不夠用時,會發生記憶體抖動,影響程式執行穩定性。

HotSpot虛擬機器在1.8之後已經取消了永久代,改為元空間,類的元資訊被儲存在元空間中。元空間沒有使用堆記憶體,而是與堆不相連的本地記憶體區域。所以,理論上系統可以使用的記憶體有多大,元空間就有多大,所以不會出現永久代存在時的記憶體溢位問題。這項改造也是有必要的,永久代的調優是很困難的,雖然可以設定永久代的大小,但是很難確定一個合適的大小,因為其中的影響因素很多,比如類數量的多少、常量數量的多少等。永久代中的後設資料的位置也會隨著一次full GC發生移動,比較消耗虛擬機器效能。同時,HotSpot虛擬機器的每種型別的垃圾回收器都需要特殊處理永久代中的後設資料。將後設資料從永久代剝離出來,不僅實現了對元空間的無縫管理,還可以簡化Full GC以及對以後的併發隔離類後設資料等方面進行優化。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 -XX:NewRatio=4:設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5 -XX:SurvivorRatio=4:設定年輕代中Eden區與Survivor區的大小比值。設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6 -XX:MaxPermSize=16m:設定持久代大小為16m。 -XX:MaxTenuringThreshold=0:設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概論。

<3>假設給你4g記憶體空間的機器,你怎麼設定堆記憶體 1,由於linux系統一般佔用1g記憶體空間,因此係統分配1g 2,線上伺服器一般是200個jvm活躍執行緒,每個執行緒棧的空間預設為1M,因此棧空間佔據200m。 3,永久代一般在50m可以滿足使用了。 4,程式計數器一般在20kb一下,因此這塊空間可以忽略。 5,因此堆可用空間為2700mb,按照新生代和老年代1:2空間設定 第五點,我的理解:新生代需要頻繁的youngc,並且還需要進行空間的壓縮,如果太大,消耗的時間太長了,老年代不需要每次都壓縮,因此在有碎片的同時,大記憶體可以放入更多的資料 備註:如果有常駐記憶體的資料,應該使用堆外記憶體,堆內空間雖然變小了,導致full gc上升,但是full gc的時間會變短(因此gc掃描的空間小了)

<6>堆外記憶體的好處是: (1)可以擴充套件至更大的記憶體空間。比如超過1TB甚至比主存還大的空間; (2)理論上能減少GC暫停時間,堆外記憶體會受gc的控制,之所以能減少gc暫停時間,如果把堆外記憶體的資料都放在堆內,每次gc都要全面掃描這塊空間,如果放在堆外,gc只會掃描那些沒有被引用的空間,因此常駐記憶體的本地快取資料使用堆外記憶體更加合適。 (3)可以在程式間共享,減少JVM間的物件複製,使得JVM的分割部署更容易實現; (4)它的持久化儲存可以支援快速重啟,同時還能夠在測試環境中重現生產數 (5)零拷貝提升資料的訪問效率

堆外記憶體的釋放過程: DirectByteBuffer物件在建立的時候關聯了一個PhantomReference,說到PhantomReference它其實主要是用來跟蹤物件何時被回收的, 它不能影響gc決策,但是gc過程中如果發現某個物件除了只有PhantomReference引用它之外,並沒有其他的地方引用它了, 那將會把這個引用(Cleaner)放到java.lang.ref.Reference.pending佇列裡, 在gc完畢的時候通知ReferenceHandler這個守護執行緒去執行一些後置處理, 而DirectByteBuffer關聯的PhantomReference是PhantomReference的一個子類, 在最終的處理裡會通過Unsafe的free介面來釋放DirectByteBuffer對應的堆外記憶體塊

參考:www.cnblogs.com/andy-zhou/p…

8,Linux與JVM的記憶體關係分析 在一些實體記憶體為8g的server上,主要執行一個Java服務,系統記憶體分配例如以下:Java服務的JVM堆大小設定為6g,一個監控程式佔用大約600m,Linux自身使用大約800m。 Linux和Java NIO在核心記憶體上開闢空間給程式使用,主要是降低不要的複製,以降低IO作業系統呼叫的開銷。比如,將磁碟檔案的資料傳送網路卡,使用普通方法和NIO時。資料流動比較下圖所看到的: 將資料在核心記憶體和使用者記憶體之間拷貝是比較消耗資源和時間的事情,而從上圖我們能夠看到。通過NIO的方式降低了2次核心記憶體和使用者記憶體之間的資料拷貝。這是Java NIO高效能的重要機制之中的一個(還有一個是非同步非堵塞)。 從上面能夠看出。核心記憶體對於Java程式效能也很重要,因此,在劃分系統記憶體使用時候。一定要給核心留出一定可用空間。 參考:www.cnblogs.com/bhlsheji/p/…

9,cms引數配置和gc日誌 blog.csdn.net/zqz_zqz/art…

10,gc調優 首先了解堆記憶體大體設定 最大堆設定,在單機web server的情況下,最大堆的設定建議在實體記憶體的1/2到2/3之間,如果是16G的實體記憶體的話,最大堆的設定應該在8000M-10000M之間。 Java程式消耗的總記憶體肯定大於最大堆設定的記憶體:堆記憶體(Xmx)+ 方法區記憶體(MaxPermSize,一般專案256M夠用了)+ 棧記憶體(Xss,包括虛擬機器棧和本地方法棧,預設配置1M的棧空間)*執行緒數 + NIO direct memory + socket快取區(receive37KB,send25KB)+ JNI程式碼 + 虛擬機器和GC本身+程式計數器(一般16kb,可以忽略不計)*執行緒數 = java的記憶體。

執行緒數一般指是業務執行緒+netty容器執行緒

舉例,16GB記憶體機器,800MB(執行緒棧)+256MB(方法區)+1000MB(linux系統)+200MB(系統預留)+200MB(JNI程式碼),留給堆記憶體可分配空間在12GB以下

進行gc調優之前應該瞭解的基本知識 <1>在進行GC優化之前,需要確認專案的架構和程式碼等已經沒有優化空間。我們不能指望一個系統架構有缺陷或者程式碼層次優化沒有窮盡的應用,通過GC優化令其效能達到一個質的飛躍。 <2>通過上述分析,可以看出虛擬機器內部已有很多優化來保證應用的穩定執行,所以不要為了調優而調優,不當的調優可能適得其反。 <3>最後,GC優化是一個系統而複雜的工作,沒有萬能的調優策略可以滿足所有的效能指標。GC優化必須建立在我們深入理解各種垃圾回收器的基礎上,才能有事半功倍的效果。 <4>gc優化兩個核心的關注點就是:降低GC頻率,縮短GC停頓時間。  在分代GC演算法中,降低迴收頻率可以通過:(1) 降低物件分配/提升率;(2) 增加代空間的大小。減少新生代大小可以縮短新生代GC停頓時間,因為這樣被複制到survivor區域或者被提升的資料更少

從微服務架構上的一些總結 <1>進行gc調優之前應該儘量思考原有的架構是否存在缺陷,程式碼有沒有優化空間。 舉例 ①假設你的服務在2000ms,那你jvm調參能有多大空間?因此這時候的著重點是在服務內部。 ②api專案往往都涉及很多服務的呼叫,如果內部都是序列化的執行邏輯導致耗時較長,會導致這些物件在young區駐留的時間較長(比如一次minor gc耗時50ms,api耗時100ms,因此在minor gc時api會延長50ms,99線就會被大幅增大,在gc的時候,eden區會向survivor遷移,如果api耗時優化在50ms以下,那服務的99線就會下降很多),因此api可以把一些邏輯併發非同步化,使得api整體耗時降了下來,在降低均線的同時把99線降低。 這種場景通常指:minor gc的耗時和服務的響應時間很接近(短耗時的服務,比gc耗時稍長一點),因此gc會延長99線,使得均線和99線差距很大(比如均線5ms,99線在55ms),如果把服務響應優化在gc時間範圍內,那麼99線將會接近於均線。 ③如果專案存在大量的短生命週期物件,並且這些物件中很多資料都是冗餘的,如果對專案進行壓測,會出現大量的minor gc,甚至還出現大量major gc,服務效能壓不上去,此時的做法可以有兩種:一、加大堆記憶體的eden區空間,使得新生代的gc頻率變的更低。二、優化依賴服務的架構,資料改成按需索取,減少短生命週期物件的佔用新生代空間。 ④如果經常有記憶體洩漏發生,應該通過記憶體資訊排查哪些記憶體沒有被正常釋放。 ⑤開啟逃逸分析,減少堆記憶體空間的消耗(www.jianshu.com/p/20bd2e9b1… <2>GC優化一般步驟可以概括為:確定目標、優化引數、驗收結果。 在網際網路應用裡面更多關注的是:低延遲。

優化案例1: Major GC和Minor GC頻繁(導致99線高居不下,api壓測期間,專案出現大量的young gc,old gc) api專案通常都是一些大量的短生命週期物件,因此年輕代應該更大一些。原因:如果eden區滿了,會向survivor遷移(資料的複製成為影響服務99線的核心因素),年輕代設定更大一些,eden物件在短週期被銷燬,不用遷移,因此99線更低。service專案通常會有一些本地快取資料,因此老年代更加大一些,甚至可以使用堆外記憶體代替老年代,減少full gc頻率。

優化案例2:請求高峰期發生GC,導致服務可用性下降(服務超時很多) GC日誌顯示,高峰期CMS在重標記(Remark)階段耗時1.39s。Remark階段是Stop-The-World(以下簡稱為STW)的,即在執行垃圾回收時,Java應用程式中除了垃圾回收器執行緒之外其他所有執行緒都被掛起,意味著在此期間,使用者正常工作的執行緒全部被暫停下來,這是低延時服務不能接受的。本次優化目標是降低Remark時間。

優化參考根據:cms在標記物件是否可達,實際上是要從新生代開始向老年代掃描的(因為有一些物件會跨代引用),因此一般在cms在併發標記之前進行一次minor gc,但是在重新標記之前,新生代同樣會產生大量的物件,因此在重新標記時,如果不再進行一次minor gc,會增加remark的stw時間,CMS提供CMSScavengeBeforeRemark引數,用來保證Remark前強制進行一次Minor GC。(優化案例減少200ms的延遲)

整體參考:www.jianshu.com/p/af2e21258… blog.csdn.net/zhoudaxia/a… blog.csdn.net/u022726695/… www.cnblogs.com/qmfsun/p/53… www.cnblogs.com/hirampeng/p… blog.csdn.net/u010556151/… 11,延遲(latency)和吞吐量(throghtput)

延遲和吞吐量,是衡量軟體系統的最常見的兩個指標。 延遲一般包括單向延遲(One-way Latency)和往返延遲(Round Trip Latency),實際測量時一般取往返延遲。它的單位一般是ms、s、min、h等。 吞吐量一般指相當一段時間內測量出來的系統單位時間處理的任務數或事務數(TPS)。注意“相當一段時間”,不是幾秒,而可能是十幾分鍾、半個小時、一天、幾周甚至幾月。它的單位一般是TPS、每單位時間寫入磁碟的位元組數等

低延遲一定意味著高吞吐量嗎?如果不是,試舉出反例。 舉例:飛機和火車在一段時間內的運貨量,飛機的速度很快,但是在較長一段時間吞吐量遠比不上火車。

參考:blog.csdn.net/lkx94/artic…

12,cpu的load比較高的原因排查 <1>首先定位是不是jvm執行緒消耗的cpu比較高(top命令),如果是,那就根據pid,即是程式id。 <2>檢視程式的所有執行緒對應的cpu時間片大小(top命令)。 <3>jstack 檢視指定執行緒id的上下文堆疊資訊,根據堆疊資訊去分析程式碼,確定是不是業務邏輯程式碼有問題。 <4>配合檢視gc log,確定是否出現頻繁的major gc,導致cpu消耗急劇上升(如記憶體洩漏,導致可用堆空間下降,導致gc頻繁出發,gc時會併發使用多核cpu,導致load比較高)。

參考demo:比如多執行緒爬第三方資料,對方響應的資料是大物件,由於網路io是流傳輸的,我是一次呼叫完,寫到記憶體才進行解析的,因此young區很快寫滿了,進而晉升到old區,結果記憶體還是不夠,接著繼續old gc,這時候cpu使用立馬飆升,電腦迅速發熱。

參考:blog.csdn.net/u012448083/…

相關文章