天天看點

java虛拟機記憶體配置設定原理概述

本文主要介紹在應用發起記憶體申請,到作業系統最終配置設定記憶體,采用了那些途徑和方法,并比較各種方法的優劣以及使用過程中應該注意那些點。

注意本文都是概述,如想詳細了解,需單獨詳細了解每一部分内容

1、應用在那些情況下發起記憶體申請

2、記憶體發起申請的步驟(3)

3、記憶體申請過程中對jvm記憶體模型的使用

4、申請記憶體用到哪些算法

5、堆和堆記憶體配置設定

1、應用發起請求,要求系統配置設定記憶體

(這裡不考慮堆外記憶體,後續單獨解釋堆外記憶體)

1)、init階段,final和static修飾的類和方法

2)、應用中new()方法

3)、建立類時建立父類的對象申請記憶體

4)、java動态建立對象時反射技術(javaassist、asm等)

2、應用發起申請一般分為3部:

1)、建立對象執行個體

2)、為此對象配置設定記憶體

3)、指針指向記憶體位址

一般我們在做單例模式或者防止出現假對象的時候,我們會用volatile來防止指令重排(關于volatile後續會專門介紹),其實表面上看就是三步變一步。

3、記憶體申請一定會涉及到對記憶體模型各部分的操作

不同的場景,對記憶體的配置設定一定是有所差別的。

1)、對象在建立階段,在resolve階段,會對靜态化屬性值進行記憶體配置設定,并初始化值=0(此時test=0),如果是有final static修飾的則将初始化值為定義值(此時test=10)

private final static int test = 10;

這樣靜态修飾的屬性值配置設定在方法區。

2)、一個方法的開始,首先是方法的參數屬性、傳回值等入棧,在棧區配置設定空間,同時如果有對象的new則需要去堆中建立對象執行個體。

3)、在堆中建立對象執行個體時,一般是兩種方式,清單和指針碰撞。在指向堆中的執行個體時候,一般也是兩種方式,直接指向(如果方法變化會需要重新指定)和通過句柄池(會使用額外的空間存儲句柄)

4)程式計數器 可以看做一個資料結構,線上程切換時候記錄下目前的位元組行數和狀态,以便于切回後能快速定位執行

4、記憶體配置設定方法(也被稱為快配置設定-tlab,慢配置設定-cas)

1)、TLAB(線程局部配置設定緩存技術))

thread local area buffer,在并發的時候為了提高記憶體配置設定的效率,在建立線程的時候為每個線程配置設定了一段記憶體,此時線程申請的時候在自己内部解決就好,這樣就不用去考慮并發問題,同時配置設定記憶體的效率跟c語言差不多。當然如果對象過大,仍然有去堆裡配置設定,是以并發問題都存在。這也是為什麼eden generation的對象越小越好

2)、cas

cas進行記憶體配置設定時,配置設定記憶體函數會傳回top_add()傳回 _top的eden區域的起始位址compare_to,當然也傳回end_add()傳回eden區域的結束值。在進行cas比較适合,compare_to與預期值進行比較,如果相同,則其他線程沒有操作該比變量。将_newtop指派給_top,将其變成真正的eden的啟示值。這種方式也就叫指針碰撞

在進行記憶體配置設定的過程中,堆是最大的,也是java開發中用的最多的,是以着重分析一下堆。

5、堆和堆記憶體配置設定

此處分三部分解釋:堆的建立 、堆記憶體的配置設定 和 垃圾回收

堆 是jvm虛拟機單獨開辟出來的一塊空間,由虛拟機自己管理,用來滿足對象執行個體的配置設定的空間需求。

堆為什麼分代? 在建立的對象中,由的對象很大,由的對象生命周期很長,而大多數對象的都會很快被回收。如果都在eden區域,eden區域采用的複制算法,對大和生命周期長的對象,是十分浪費cpu等資源的。是以将對象分代,不同代采用不同的算法回收。

1)、堆的初始化

堆是universe根據initial_heap來完成java堆初始化,具體要根據虛拟機配置。當然會根據回收算法,配置回收政策。

eg:若虛拟機配置UseParallelGC,則Java堆的堆類型為ParallelScavengeHeap(并行收集堆)

預設虛拟機将使用GenCollectedHeap(分代收集堆)

其具體算法、堆類型和回收政策如下:

java虛拟機記憶體配置設定原理概述

需要注意的一點就是,堆空間初始化完成以後,如果是64位環境會進行指針壓縮和TLAB等相關内容。通常64位JVM消耗的記憶體會比32位的大1.5倍,這是因為在64位環境下,對象将使用64位指針,這就增加了一倍的指針占用記憶體開銷。

2)、堆記憶體配置設定

堆在初始化完成以後,我們以GenCollectedHeap 分代堆為例來解釋,一般GenCollectedHeap支援10個分代(一般隻使用新生代和老年代)

那麼GenCollectedHeap是如何向系統申請記憶體空間的呢?

答案就在allocate()函數中 ,在申請之前,當然要對記憶體空間的大小和分塊數進行計算

預設的新生代和老年代的分塊數為1,而永久代的分塊數為2

後邊涉及到的内容很多,需要篇幅較長,後續再解釋。下面copy一張圖過來,有興趣的朋友可以看下

java虛拟機記憶體配置設定原理概述

3)、堆回收有更多gc的内容,會單獨介紹