Java 記憶體分析(程式例項),學會分析記憶體,走遍天下都不怕!!!
相信大多數的java初學者都會有這種經歷:碰到一段程式碼的時候,不知該從何下手分析,不知道這段程式碼到底是怎麼執行最後得到結果的..... 等等吧,很多讓人頭疼的問題,作為一名合格的程式設計師呢,遇到問題一定要思路清晰,不要將錯就錯,矇混過關,這一點很重要!
鑑於筆者最近惡補了java基礎,在這兒給大家總結了一些java程式碼記憶體分析的經驗,希望可以對家有所幫助。
在分析記憶體之前呢,通過這個圖讓大家明白計算機是怎麼處理一個程式的。
簡單總結一下:1.計算機首先從硬碟拿到程式,載入(load)到記憶體區
2.作業系統找到main方法開始執行
3.執行程式碼過程中的記憶體管理:
記憶體中分四塊:分別是heap(堆)、stack(棧)、data segment
(資料區)、code segmen(程式碼區),各個區所儲存的內容圖中已
標註。
接下來,
給大家舉幾個例子程式,分別進行記憶體分析
**
程式一:
**
public class Person {
static int id;
static int age;
Person(int _id, int _age) {
id = _id;
age = _age;
}
public static void main(String[] args) {
Person tom = new Person(1,25);
System.out.println("id= " + id + " age= " + age);
}
}
記憶體分析圖:
第一步:從main方法入手,首先看到要實現一個Person類物件,該物件的名字是tom,則在stack中立馬分配出一塊空間用來存tom這個物件名,它指向heap中的Person物件(上文提到heap中儲存new出來的東西)
注:圖中區域性變數tom的內容為xxx,xxx實際為Person物件在堆中的地址,在此處用xxx
代替。
第二步:呼叫Person中的構造方法,此時定義了兩個區域性變數(要儲存到stack中)_id和_age,則立馬在stack中分配兩塊空間用於儲存這兩個區域性變數,接下來把1和25分別傳給這兩個變數
第三步:執行id=_id; age=_age;這兩句,把_id和_age的值傳給Person物件
第四步:區域性變數_id和_age消失(java的垃圾回收機制)
到此完成Person物件的建立,執行輸出語句,程式執行結束。
**
程式二:super引用,動態繫結及多型
**
說到多型,就先給大家鞏固一下動態繫結和多型的概念吧:
動態繫結:在執行期間(非編譯期間)判斷所引用物件的實際型別,根據其實際的型別呼叫其相應的方法。
簡單的來說,動態繫結就是根據實際new的物件所屬的類呼叫方法,幫助程式的擴充套件性達到極致。
多型的話,要知道多型產生的三個條件:
1.有繼承
2.有重寫
3.父類引用指向子類物件
//abstract 關鍵字 ---> 抽象類,抽象類一定被繼承,抽象方法一定被重寫
class Animal { //可以這樣宣告 absrtact class Animal
private String name;
Animal(String name) {
this.name = name;
}
public void enjoy() {
System.out.println("叫聲...");
}
}
class Cat extends Animal{
private String eyescolor;
Cat(String n,String c) {
super(n);
eyescolor = c;
}
public void enjoy() {
System.out.println("貓叫聲....");
}
}
class Dog extends Animal {
private String furCorlor;
Dog(String n, String f) {
super(n);
furCorlor = f;
}
public void enjoy() {
System.out.println("狗叫聲...");
}
}
class Lady {
private String name;
private Animal pet;
Lady(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
public void petEnjoy() {
pet.enjoy();
}
}
public class TestAnimal {
public static void main(String[] args) {
Cat c = new Cat("catname","blue");
Dog d = new Dog("dogname","black");
c.enjoy();
d.enjoy();
Lady l1 = new Lady("l1",c);
Lady l2 = new Lady("l2",d);
l1.petEnjoy();
l2.petEnjoy();
}
}
記憶體分析圖:
老樣子,從main方法入手:
第一步,Cat c = new Cat("catname","blue");
當你new一個子類物件出來的時候,其內部就已經包含父類物件,並且該子類物件的super引用會指向其父類物件。
此處分兩小步給大家說明:(stack中區域性變數的產生過程不再贅述,可參考程式一)
(1)new出來的Cat物件中包含Animal物件,Animal物件有一個成員變數name,自動初始化為null,C物件有一個成員變數eyescolor,自動初始化為null。
(2)傳“catname”和“blue”這兩個引數,“blue”直接賦值給eyescolor,而“catname”傳進去後,通過super(n)方法,呼叫其父類物件的構造方法,使name值等於“catname”
第二步,Dog d = new Dog("dogname","black");
記憶體分析同第一步(此處省略)
第三步,c.enjoy(); d.enjoy();
此時,c指向的是Cat物件,但是Cat物件中包含其父類Animal物件,這兩個物件都含有enjoy()方法,那麼應該呼叫哪一個呢?這就涉及到了java的動態繫結機制了,你new出來的物件實際是Cat物件,那麼就呼叫Cat物件的方法,而不是呼叫Animal物件的enjoy()方法;同理,d.enjoy()呼叫的是Dog物件的enjoy()方法。
所以,輸出結果為
第四步,Lady l1 = new Lady("l1",c);
Lady l2 = new Lady("l2",d);
記憶體分析圖:
此處Animl的引用pet指向了其子類物件c,也就是上文提到的“父類引用指向子類物件”,並且,有繼承、有重寫,這就是多型。
每一個創建出來的物件都有this引用,指向他自身
第五步, l1.petEnjoy();
l2.petEnjoy();
呼叫Lady的petEnjoy()方法,輸出對應資訊,最終輸出結果為
程式三:陣列
3.1 一維陣列
public class Test {
public static void main(String[] args) {
Date[] days = new Date[3];
for(int i=0;i<3;i++) {
days[i] = new Date(2020,4,i+1);
}
}
}
class Date {
int year,month,day;
Date(int y, int m, int d) {
year = y;
month = m;
day = d;
}
}
記憶體分析圖
days指向堆中的一個數組,該陣列中存放的是每一個Date物件的地址,每一個地址指向一個Date物件。(詳細賦值過程可參考程式一)
3.2 二維陣列
二維陣列實際上是一維陣列的陣列
public class Array {
public static void main(String[] args) {
int[][] a = new int[][] {
{1,2,3},
{4,5,6},
{7,8,9}
};
}
}
記憶體分析圖
棧空間中a指向一個一維陣列,該陣列每塊區域存放的是int型陣列的地址。
筆者創作初期,如有不當之處,望批評指正。