前言
本文不會深入原理去講解,而是通過具體的場景(代碼)來理解java的內(nèi)存區(qū)域和類加載機制
java內(nèi)存區(qū)域劃分
從jvm角度看芳绩,分為堆掀亥,棧,方法區(qū)示括,本地方法棧铺浇,程序計數(shù)器
從操作系統(tǒng)角度看痢畜,分為堆垛膝,棧,數(shù)據(jù)段丁稀,代碼段
程序計數(shù)器
線程私有吼拥,指示的是當前線程字節(jié)碼執(zhí)行到的位置
虛擬機棧
線程私有,描述的是java方法執(zhí)行的內(nèi)存模型线衫,每一個方法從執(zhí)行到結(jié)束凿可,都對應一個方法棧幀的入棧和出棧,方法中的局部變量就存儲在棧中授账,當然指的是基本類型和引用類型的引用枯跑,實例還是存放在堆中
棧是一塊連續(xù)的內(nèi)存區(qū)域,大小由操作系統(tǒng)決定白热,不會產(chǎn)生碎片
本地方法棧
和虛擬機棧的作用類似敛助,只不過本地方法棧是為jvm使用到的Native方法服務
堆
線程共享區(qū)域,存放著幾乎所有的實例
一塊大但是不連續(xù)的內(nèi)存區(qū)域屋确,容易產(chǎn)生碎片纳击,一般說的內(nèi)存泄漏,主要指的就這塊
方法區(qū)
線程共享區(qū)域攻臀,存儲已經(jīng)被jvm加載的類的信息焕数,常量,靜態(tài)變量刨啸,編譯器編譯后的代碼等數(shù)據(jù)
運行時常量池就是方法區(qū)的一部分堡赔,用于存放各種字面量和符號引用
字面量:基本類型的包裝類,字符串類型设联,final修飾的常量
代碼示例
public class People{
int a = 1;
final int b=2;
static int c=3;
Student s1 = new Student();
public void XXX(){
int d = 1;
Student s2 = new Student();
}
}
問a善已,b,c仑荐,d的內(nèi)存在哪里雕拼,s1,s2的內(nèi)存在哪里粘招?
記住下面幾句話啥寇。
成員變量全部存儲在堆中(包括基本數(shù)據(jù)類型,引用及引用的對象實體),因為他們屬于對象辑甜,對象都是存儲在堆中的
局部變量的基本數(shù)據(jù)類型和引用存儲于棧當中衰絮,引用的對象實體存儲在堆中。因為他們屬于方法當中的變量磷醋,生命周期會隨著方法一起結(jié)束猫牡。
常量,靜態(tài)變量(屬于類)存放在常量池
存放在堆中的有a邓线,s1和s1的實例淌友,s2的實例
存放在棧中的有d,s2
存放在方法區(qū)(常量池)的有b骇陈,c
類加載
什么是類加載
類加載指的是將.class文件的二進制數(shù)據(jù)讀取到內(nèi)存中震庭,將其放入內(nèi)存區(qū)域中的方法區(qū),然后在堆中創(chuàng)建一個Class對象你雌,這就是我們說的任何類都是Class類的一個實例的原因
什么時候進行類加載
簡單說就是類在用到的時候才會被加載器联,下面我來列舉一些情況來說明什么叫被用到
- 創(chuàng)建類的實例
- 調(diào)用類的靜態(tài)變量(final修飾的靜態(tài)變量除外)
- 調(diào)用類的靜態(tài)方法
- 反射如Class.forName(com.xxx.xxx)
類加載過程
1.類加載
類的加載是由類加載器來完成加載的
說到類加載,我們來簡單說下雙親委派機制
java自帶的3中類加載器分別是AppClassLoader婿崭,ExtClassLoader和Bootstrap
我們自己寫的類首先會調(diào)用AppClassLoader去加載自身拨拓,但它不會馬上加載,而是調(diào)用ExtClassLoader去加載氓栈,它也不會馬上加載渣磷,而是調(diào)用Bootstrap去加載,如果Bootstrap加載不了颤绕,就讓ExtClassLoader去加載幸海,還加載不了就讓AppClassLoader加載
2.類的連接
類連接有3步,分別是驗證奥务,準備物独,解析
這里我只說明一下準備階段做的工作
為類的靜態(tài)變量分配內(nèi)存并賦值默認的初始值
public static int value=123;
在準備階段value的值是0
3.類的初始化(順序)
沒有父類的情況
類的靜態(tài)屬性
類的靜態(tài)代碼塊
類的非靜態(tài)屬性
類的非靜態(tài)代碼塊
構(gòu)造方法有父類的情況
父類的靜態(tài)屬性
父類的靜態(tài)代碼塊
子類的靜態(tài)屬性
子類的靜態(tài)代碼塊
父類的非靜態(tài)屬性
父類的非靜態(tài)代碼塊
父類的構(gòu)造方法
子類的非靜態(tài)屬性
子類的非靜態(tài)代碼塊
子類的構(gòu)造方法
代碼示例1
public class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
結(jié)果輸出
count1=1
count2=0
結(jié)果分析
1.調(diào)用類的靜態(tài)方法會觸發(fā)類的加載
2.在準備階段為靜態(tài)變量分配內(nèi)存并賦值默認初始值
singleTon=null;
count1=0;
count2=0;
3.初始化操作(賦值操作),初始化是從上到下依次執(zhí)行的
先調(diào)用new操作氯葬,count1=1挡篓,count2=1
count1沒有賦值操作
count2進行賦值操作
修改代碼如下
public class SingleTon {
public static int count1;
public static int count2 = 0;
private static SingleTon singleTon = new SingleTon();
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
結(jié)果輸出
count1=1
count2=1
代碼示例2
public class HelloA {
public static int a=1;
static{
System.out.println("Static A");
}
{
System.out.println("I am A class");
}
public HelloA(int i){
System.out.println("Hello A");
}
}
public class HelloB extends HelloA{
public static int b=0;
static{
System.out.println("Static B");
}
{
System.out.println("I am B class");
}
public HelloB(){
super(1);
System.out.println("Hello B");
}
public static void main(String[] args) {
new HelloB();
//HelloB.a=1;
}
}
輸出結(jié)果
Static A
Static B
I am A class
Hello A
I am B class
Hello B
結(jié)果分析
參考前面類的初始化順序