Java進階---對象與記憶體控制(一)
Java提供了優秀的記憶體回收機制來回收已經分配的記憶體,但是這並不是意味著我們在編程程式的過程中,就可以肆無忌憚地揮霍Java程式的記憶體配置,這樣做會造成程式的運行效率低下,直接影響程式的整體使用者體驗。
Java的記憶體管理機制分為記憶體配置和記憶體回收機制。記憶體配置機制在Java對象建立時為該對象在堆記憶體中分配指定的記憶體空間。記憶體回收指的是當該Java對象失去引用,變成垃圾時,記憶體回收機制會自動清理該對象,並回收該對象所佔用的記憶體。
Java的記憶體回收機制由一條後台線程完成,本身也是非常消耗效能的,因此如果肆無忌憚地建立對象,讓系統分配記憶體,那這些分配的記憶體都將由記憶體回收機制進行會回收,這樣不但會使系統中可用記憶體減少,降低程式運行效能,而且使得記憶體回收的負擔加重,降低程式的運行效能。
1、執行個體變數和類變數
Java程式的變數大體可分為成員變數和局部變數,局部變數可分為3類:
>形參:在方法簽名中定義的局部變數,由方法調用者為其賦值,隨方法的結束而消亡;
>方法內的局部變數:必須在方法內對其進行顯示初始化,從初始化完成後開始生效,隨方法的結束而消亡;
>代碼塊內的局部變數:必須在代碼塊內對其進行顯示初始化,從初始化完成後開始生效,隨代碼塊的結束而消亡。
由上可知,局部變數是指在方法簽名、方法內和代碼塊內定義的變數。被儲存在方法的棧記憶體中。
成員變數是指定義在類體內的變數,儲存在類的棧中。如果定義該成員變數時沒有使用static修飾,該成員變數又被稱為非靜態變數或執行個體變數,否則就被稱為靜態變數或類變數。
1)、執行個體變數和類變數的屬性
類變數屬於該類本身,而執行個體變數屬於該類的執行個體。在同一個JVM內,每個類只對應一個Class對象,但每個類可以建立多個Java對象。
由於同一個JVM內每個類只對應一個Class對象,因此用一個JVM內的一個類的類變數只需一塊記憶體空間;但對於執行個體變數而言,該類每建立一次執行個體,就需要為執行個體變數分配一塊記憶體空間。也就是說程式中有幾個執行個體,執行個體變數就需要幾塊記憶體空間。
通過下面的程式可以很好地理解執行個體變數和類變數:
class NoFive{ String name; int id; static int age; public void output(){ System.out.println("姓名:" + name + ",學號:" + id); }} public class test01 { public static void main(String[] args) { //類變數屬於該類本身,只要該類初始化完成,程式即可使用類變數 NoFive.age = 22; //通過類訪問類變數 System.out.println("NoFive的年齡age是:" + NoFive.age); NoFive noFive = new NoFive(); noFive.name = "小武靈靈"; noFive.id = 41009160; noFive.age = 22; //通過noFive訪問NoFive類的age類變數 System.out.println("通過noFive變數訪問age類變數:" + noFive.age); noFive.output(); NoFive noFive2 = new NoFive(); noFive2.name = "靈靈小武"; noFive2.id = 6190014; noFive2.age = 23; noFive2.output(); //通過noFive2修改NoFive類的age類變數 noFive2.age = 21; //分別通過noFive、noFive2和NoFive訪問NoFive類的age類變數 System.out.println("通過noFive變數訪問age類變數:" + noFive.age); System.out.println("通過noFive2變數訪問age類變數:" + noFive2.age); System.out.println("通過NoFive類訪問age類變數 :" + NoFive.age); }}
2)、執行個體變數的初始化時機
程式運行中,每次建立Java對象都會為執行個體變數分配記憶體空間,並對執行個體變數執行初始化。在代碼中,程式可以在3個地方對執行個體變數執行初始化:
l 定義執行個體變數時指定初始值;
l 非靜態初始化塊中對執行個體變數指定初始值;
l 構造器中對執行個體變數指定的初始化。
其中前兩種方式比最後一種方式更早執行,但前兩種方式的執行順序與它們在來源程式中的排列順序相同。例如:
class NoFive{ { name = “小武靈靈”;}String name = “靈靈小武”;public void output(){System.out.println(name);}}
上面程式output方法輸出的name值應該為”靈靈小武”.
3)、類變數的初始化時機
在程式中,JVM對一個Java類只初始化一次,因此Java程式每運行一次,系統只為類變數分配一次記憶體空間,執行一次初始化。在代碼中,程式可以在2個地方對類變數執行初始化:
l 定義類變數時指定初始值;
l 靜態初始化塊中對類變數指定初始值。
這兩種方式的執行順序與它們在來源程式中的排列順序相同。下面是一個例子:
public class test01 { //定義時指定初始值 static String name = "小武"; //通過靜態初始化塊為id類變數指定初始值 static{ System.out.println("靜態初始化塊"); id = 6190014; } static int id = 41009160; public static void main(String[] args) { System.out.println("name類變數的值:" + test01.name); System.out.println("id類變數的值:" + test01.id); }}
上面程式輸出結果為“小武”和“41009160”。程式執行的順序為:系統先為所有類變數分配記憶體空間,再按原始碼中的排列順序執行靜態初始化塊中所指定的初始值和定義類變數時所指定的初始值。
下面借用一個典型的例子來更好地理解類變數的初始化過程。首先定義了Price類,該Price類裡有一個靜態initPrice變數,用於代表初始價格。每次建立Price執行個體時,系統會以initPrice為基礎,減去當前打折價格(由discount參數代表)即得到該Price的currentPrice變數值。
class Price{ //類成員是Price執行個體 final static Price INSTANCE = new Price(2.8); //再定義一個類變數 static double initPrice = 20; //定義該Price的currentPrice執行個體變數 double currentPrice; public Price(double discount) { //根據靜態變數計算執行個體變數 currentPrice = initPrice - discount; }} public class test01 { public static void main(String[] args) { //通過Price的INSTANCE訪問currentPrice執行個體變數 System.out.println(Price.INSTANCE.currentPrice); //顯示建立Price執行個體 Price p = new Price(2.8); //通過顯示建立的Price執行個體訪問currentPrice執行個體變數 System.out.println(p.currentPrice); }}
表面上看,程式輸出兩個Price的currentPrice都應該返回17.2(20-2.8),但實際上運行後輸出結果為-2.8和17.2。
如果僅僅停留在代碼錶面來看這個問題,很難得到正確結果,下面將從記憶體角度來分析這個程式。
首先看PriceTest類中的主函數,執行System.out.println(Price.INSTANCE.currentPrice);時程式會第一次用到Price類,這個時候會對Price類進行初始化,初始化過程為:
A. 為Price的兩個類變數(INSTANCE和initPrice)分配記憶體空間,此時INSTANCE和initPrice的值為預設值null和0.0;
B. 按照初始化代碼(定義時指定初始值和初始化塊中執行初始值)的排列順序對類變數執行初始化:
a) 對INSTANCE執行初始化:建立Price執行個體用到Price類的帶參數構造器,執行其中的currentPrice=initPrice-discount。因為此時的initPrice=0.0,所以currentPrice=-2.8;
b) 對initPrice執行初始化:initPrice=20;
C. 這個時候主函數main()中System.out.println(Price.INSTANCE.currentPrice);執行完畢,出書結果為-2.8。
D. 之後執行Price p = new Price(2.8);這行代碼,會先執行ABC過程,此時currentPrice=-2.8,initPrice=20。然後調用Price類的帶參構造器執行currentPrice = initPrice – discount,得到currentPrice=17.2。