線程共享區(qū)域:方法區(qū),堆
線程私有區(qū)域:程序計(jì)數(shù)器,本地方法棧,虛擬機(jī)棧

虛擬機(jī)棧
線程私有,生命周期與線程一致。每調(diào)用一個(gè)方法就可以創(chuàng)建一個(gè)新的棧幀,棧幀中存放局部變量表(基本類(lèi)型數(shù)據(jù)和對(duì)象引用)、操作數(shù)棧和方法出口等信息。大小可動(dòng)態(tài)擴(kuò)展。
在編譯期間分配內(nèi)存
當(dāng)棧調(diào)用深度大于JVM所允許的范圍,會(huì)拋出StackOverflowError的錯(cuò)誤;當(dāng)申請(qǐng)不到空間時(shí),會(huì)拋出 OutOfMemoryError。
本地方法棧
空間較小,用于獲取要執(zhí)行的字節(jié)碼指令。
PC 寄存器:
也叫程序計(jì)數(shù)器。JVM支持多個(gè)線程同時(shí)運(yùn)行,每個(gè)線程都有自己的程序計(jì)數(shù)器。倘若當(dāng)前執(zhí)行的是 JVM 的方法,則該寄存器中保存當(dāng)前執(zhí)行指令的地址;倘若執(zhí)行的是native 方法,則PC寄存器中為空。
堆
虛擬機(jī)中內(nèi)存最大的一塊,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,用于存儲(chǔ)方法實(shí)例??梢詣澐殖龆鄠€(gè)線程私有的緩沖區(qū),可以處于物理上不連續(xù),邏輯上連續(xù)的空間中。
常量池位于堆中。JVM為每個(gè)已加載的類(lèi)型維護(hù)一個(gè)常量池。存儲(chǔ)直接常量(基本類(lèi)型 、String)和對(duì)其他類(lèi)型方法、字段的符號(hào)引用。在Java動(dòng)態(tài)鏈接中起核心作用。
方法區(qū)
主要用于存儲(chǔ)類(lèi)的信息、常量池、方法數(shù)據(jù)、方法代碼等。方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進(jìn)行區(qū)分,通常又叫“非堆”。
小結(jié):
1 實(shí)例位于棧中,對(duì)象在堆中。操作實(shí)例,實(shí)際上是通過(guò)實(shí)例的指針間接操作對(duì)象。
2 棧中的數(shù)據(jù)和堆中的數(shù)據(jù)銷(xiāo)毀不同步。
方法一但結(jié)束,棧中的局部變量立即銷(xiāo)毀,然鵝可能還有其他變量指向堆中的對(duì)象,知道棧中沒(méi)有任何對(duì)象指向它,在垃圾回收時(shí),才能被銷(xiāo)毀。
3 每個(gè)JVM實(shí)例都有自己的內(nèi)存區(qū)域,且互不影響。(這些內(nèi)存區(qū)域都是線程共享的)
4 類(lèi)的成員變量在不同的對(duì)象中個(gè)不相同,存儲(chǔ)在堆中的對(duì)象里。而方法這是使用時(shí)才壓入棧中。
普通Java對(duì)象的創(chuàng)建過(guò)程
虛擬機(jī)在遇到new指令時(shí),
- 檢查該指令參數(shù)是否可以在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并檢查它是否被加載、解析或者初始化過(guò),如果沒(méi)有字執(zhí)行類(lèi)加載。
- 為新生對(duì)象分配內(nèi)存,即將一塊確定大小的內(nèi)存從Java堆中劃分出來(lái)(在類(lèi)加載時(shí)確定其大?。?br>
>指針碰撞: Java堆中的內(nèi)存絕對(duì)規(guī)整,用過(guò)的在一邊,未用的在另一邊,中間放置一個(gè)指針作為指示器。分配內(nèi)存即將指針移向所用空間的距離。
>空閑列表:Java堆中的內(nèi)存并不規(guī)整,虛擬機(jī)維護(hù)一個(gè)列表類(lèi)似記錄哪塊內(nèi)存是用過(guò)的哪塊是沒(méi)有的,分配時(shí)從中找到一個(gè)足夠大的區(qū)域劃分給對(duì)象,并且更新列表。 - 解決并發(fā)產(chǎn)生的問(wèn)題
>在分配內(nèi)存空間時(shí)做同步處理,即采用CAS和失敗重試的方式保證更新時(shí),操作的原子性。
>將該動(dòng)作劃分在不同的空間中進(jìn)行。 - 將分配的內(nèi)存空間初始化為0
- 對(duì)對(duì)象進(jìn)行必要設(shè)置(此時(shí),虛擬機(jī)看來(lái),對(duì)象已經(jīng)產(chǎn)生,但是對(duì)于程序而言,還沒(méi)有產(chǎn)生)
- 執(zhí)行<init>()
類(lèi)加載的過(guò)程
案例分析:
class Singleton{
private static Singleton singleton=new Singleton();
public static int count_1;//1
public static int count_2=0;//0
static{
count_1++;
count_2++;
}
private Singleton(){
count_1++://1
count_2++;//1
}
public static Singleton getInstance(){
return singleton;
}
}
public class ClassLoaderProcess{
public static void main(String[] args){
System,out.println(count_1);//2
System,out.println(count_2);//1
}
}
- 準(zhǔn)備階段:private Singleton()方法將count_1和count_2賦為零值
- 初始化階段:Java虛擬機(jī)按順序執(zhí)行
>實(shí)例化private Singleton()方法,count_1++;//1、 count_2++;//1
>按順序執(zhí)行static代碼,靜態(tài)變量的值賦為代碼中的初始化值:count_1;//1、count_2=0;
>執(zhí)行靜態(tài)代碼塊中的代碼,即自增,count_1=2;、count_2=1;
具體內(nèi)容可以看下面的鏈接,詳細(xì)的不能再詳細(xì)的那種
Java虛擬機(jī)類(lèi)加載機(jī)制——案例分析
內(nèi)存溢出
- Java堆內(nèi)存溢出
public class HeapOOM{
static class OOMObject{}
public static void main(String[] args){
List<OOMObject> list=new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject);
}
}
}
Java堆用來(lái)存儲(chǔ)對(duì)象實(shí)例,只要不斷的創(chuàng)建對(duì)象,并且保證GCRoots到對(duì)象之間都有可達(dá)路徑來(lái)避免垃圾回收機(jī)制來(lái)清楚這些對(duì)象,就會(huì)造成OOM
- 虛擬機(jī)棧和本地方法棧溢出
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverFlowError;
減少棧內(nèi)存容量或者定義大量的本地變量(導(dǎo)致棧幀變大)會(huì)導(dǎo)致以上錯(cuò)誤
public class JavaVMStackSOF{
private int stackLength=1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args){
JavaVMStackSOF oom=new JavaVMStackSOF();
try{
oom.stackLeak();
} catch(Throwable e){
System,out.println("stack length: "+oom.stackLength);
throw e;
}
}
如果虛擬機(jī)在擴(kuò)展棧的時(shí)候,無(wú)法申請(qǐng)到足夠的內(nèi)存空間,則拋出OutOfMemoryError。
多線程中,每個(gè)線程分配的棧內(nèi)存越大,越容易產(chǎn)生內(nèi)存溢出。
解決方案:減少最大堆和減少棧容量來(lái)?yè)Q取更多的線程。
public class JavaVMStackOOM{
private void dontStop(){
while(true){}
}
public void stackLeakByThread(){
while(true){
Thread thread=new Thread(new Runnable(){
public void run(){
dontStop();
}
}
);
thread.start();
}
stackLength++;
stackLeak();
}
public static void main(String[] args){
JavaVMStackOOM oom=new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
- 方法區(qū)和運(yùn)行時(shí)常量池溢出
- 本機(jī)直接內(nèi)存溢出
內(nèi)存泄露
- 只要類(lèi)自己管理內(nèi)存需要警惕內(nèi)存泄露問(wèn)題。
一旦元素被釋放掉,該元素中包含的任何引用都要被清空
E.G.Stack類(lèi)自己管理內(nèi)存,存儲(chǔ)池中包含了elements數(shù)組(對(duì)象引用單元而不是對(duì)象本身的元素)的元素,數(shù)組活動(dòng)區(qū)域中的元素時(shí)已經(jīng)分配好的,而數(shù)據(jù)其余部分的元素是自由的。但是垃圾回收器對(duì)他們一視同仁,即elements的所有元素都同等有效。
Solution:在數(shù)組元素變成非活動(dòng)部分時(shí),程序員手動(dòng)清除這些元素
- 緩存也會(huì)造成內(nèi)存泄露
一旦對(duì)象進(jìn)入緩存就很容易被遺忘
Solution_1:用WeakHashMap代表緩存。只要在緩存之外存在對(duì)某個(gè)對(duì)象的key的引用,該項(xiàng)就有意義。當(dāng)緩存中的項(xiàng)過(guò)期后,他們會(huì)自動(dòng)被刪除。
當(dāng)索要緩存的項(xiàng)的生命周期是由該鍵的外部引用決定,而不是緩存項(xiàng)的值決定時(shí),WeakHashMap才有用
Solution_2:由后臺(tái)線程定期清除或者在給緩存中添加新的條目的時(shí)候順便清理。(LinkedHashMap利用其removeEldestEntry實(shí)現(xiàn))
Solution_3:對(duì)于復(fù)雜內(nèi)存,java.lang.ref解決
- 監(jiān)聽(tīng)器與其他回調(diào)
E.G.如果實(shí)現(xiàn)了一個(gè)api,客戶端在改api調(diào)用中注冊(cè)回調(diào),卻沒(méi)有顯示的取消,除非采取某些動(dòng)作,否則他們會(huì)繼續(xù)聚集。
Solution:保存其弱引用,即將他們作為WeakHashMap中的鍵即可。