Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而存在,有些區域則依賴用戶線程的啓動和結束而創建和銷燬。根據《Java虛擬機規範(Java SE 7版)》的規定,Java虛擬機所管理的內存將會包括如下幾個運行時數據區域,以下圖所示:java
咱們能夠將上面的數據區域分爲線程獨有、線程共享及其餘三大區域:算法
Java是一門面向對象的語言,在Java程序運行的過程當中無時不刻都有對象被建立。在語言層面,建立對象一般是一個new關鍵字,可是,在虛擬機中,建立對象包括以下流程:shell
類加載 --> 分配內存 --> 內存空間初始化零值 --> 對象頭設置 --> init初始化數組
分配內存的方式爲:
「指針碰撞」:在內存規整狀況下,將指針向空閒空間挪動一段與對象大小相等的距離。
「空閒列表」:在內存不規整狀況下,虛擬機維護一個記錄內存可用的列表,分配的時候從列表中找到一塊空間劃分給對象。
併發狀況下的內存分配:
同步:對分配內存空間的動做進行同步處理———採用CAS配上失敗重試的方式,保證更新操做的原子性
本地線程分配緩衝(TLAB):把內存分配動做按照線程劃分在不一樣空間中。即每一個線程在Java堆中預先分配一塊內存TLAB,只有TLAB用完並從新分配新的TLAB時才須要同步。bash
在HotSpot虛擬機中,對象在內存中的存儲佈局能夠分爲3塊區域:對象頭(Header)、實例數據(Instance Data)和對其填充(Padding)數據結構
創建對象是爲了使用對象。咱們的Java程序須要經過棧上的reference數據來操做堆上的具體對象。目前主流的訪問方式有兩種:句柄和直接指針。併發
句柄:Java堆中會劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址。句柄中包含了對象實例數據與類型數據各自的具體地址。佈局
直接訪問:reference指針存儲的直接就是對象地址測試
使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(如垃圾收集時)時只會改變句柄中的實例數據指針,而reference自己不須要修改優化
直接訪問最大的好處就是速度快。節省了一次指針定位的時間開銷。HotSpot虛擬機使用第二種方式進行對象的訪問。
-Xms 堆最小值 -Xmx 堆最大值 -XX:HeapDumpOnOutOfMemoryError能夠在虛擬機出現異常時將堆存儲快照
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
複製代碼
public class HeadOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
複製代碼
運行結果:
-Xss 設置棧的大小
-Xss228k
複製代碼
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength ++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF stackSOF = new JavaVMStackSOF();
stackSOF.stackLeak();
}
}
複製代碼
運行結果:
實驗結果代表,在單線程下,當內存沒法分配的時候,虛擬機拋出的都是StackOverflow異常
測試:建立線程致使內存溢出異常
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
new Thread(() -> {
dontStop();
}).start();
}
}
public static void main(String[] args) {
JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
javaVMStackOOM.stackLeakByThread();
}
}
複製代碼
String.intern()方法返回的是常量池中的對象,若是池中沒有對象,則建立對象返回引用
在JDK 1.6及以前的版本中,因爲常量池分配在永久代內,咱們能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區大小,從而間接限制其中常量池的容量,測試代碼:
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
複製代碼