首先我們要明白一點,我們所使用的變數就是一塊一塊的記憶體空間!!
一、記憶體管理原理:
在java中,有java程式、虛擬機器、作業系統三個層次,其中java程式與虛擬機器互動,而虛擬機器與作業系統間互動!這就保證了java程式的平臺無關性!下面我們從程式執行前,程式執行中、程式執行記憶體溢位三個階段來說一下記憶體管理原理!
1、程式執行前:JVM向作業系統請求一定的記憶體空間,稱為初始記憶體空間!程式執行過程中所需的記憶體都是由java虛擬機器從這片記憶體空間中劃分的。
2、程式執行中:java程式一直向java虛擬機器申請記憶體,當程式所需要的記憶體空間超出初始記憶體空間時,java虛擬機器會再次向作業系統申請更多的記憶體供程式使用!
3、記憶體溢位:程式接著執行,當java虛擬機器已申請的記憶體達到了規定的最大記憶體空間,但程式還需要更多的記憶體,這時會出現記憶體溢位的錯誤!
至此可以看出,Java 程式所使用的記憶體是由 Java 虛擬機器進行管理、分配的。Java 虛擬機器規定了 Java 程式的初始記憶體空間和最大記憶體空間,開發者只需要關心 Java 虛擬機器是如何管理記憶體空間的,而不用關心某一種作業系統是如何管理記憶體的。
二、 RUNTIME 類的使用:
Java 給我們提供了Runtime 類得到JVM 記憶體的資訊
方法名稱 | 引數 | 作用 | 返回值 |
getRuntime | 無 | 獲取Runtime 物件 | Runtime 物件 |
totalMemory | 無 | 獲取JVM 分配給程式的記憶體數量 | long:記憶體數量 |
freeMemory | 無 | 獲取當前可用的記憶體數量 | long:記憶體數量 |
maxMemory | 無 | 獲取JVM 可以申請到的最大記憶體數量 | long:記憶體數量 |
三、記憶體空間邏輯劃分:
JVM 會把申請的記憶體從邏輯上劃分為三個區域,即:方法區、堆與棧。
方法區:方法區預設最大容量為64M,Java虛擬機器會將載入的java類存入方法區,儲存類的結構(屬性與方法),類靜態成員等內容。
堆:預設最大容量為64M,堆存放物件持有的資料,同時保持對原類的引用。可以簡單的理解為物件屬性的值儲存在堆中,物件呼叫的方法儲存在方法區。
棧:棧預設最大容量為1M,在程式執行時,每當遇到方法呼叫時,Java虛擬機器就會在棧中劃分一塊記憶體稱為棧幀(Stack frame),棧幀中的記憶體供區域性變數(包括基本型別與引用型別)使用,當方法呼叫結束後,Java虛擬機器會收回此棧幀佔用的記憶體。
四、java資料型別
1、基本資料型別:沒封裝指標的變數。
宣告此型別變數,只會在棧中分配一塊記憶體空間。
2、引用型別:就是底層封裝指標的資料型別。
他們在記憶體中分配兩塊空間,第一塊記憶體分配在棧中,只存放別的記憶體地址,不存放具體數值,我們也把它叫指標型別的變數,第二塊記憶體分配在堆中,存放的是具體數值,如物件屬性值等。
3、下面我們從一個例子來看一看:
public class Student { String stuId; String stuName; int stuAge; } public class TestStudent { public static void main(String[] args) { Student zhouxingxing = new Student(); String name = new String("旺旺"); int a = 10; char b = 'm'; zhouxingxing.stuId = "9527"; zhouxingxing.stuName = "周星星"; zhouxingxing.stuAge = 25; } }
(1)類當然是存放在方法區裡面的。
(2)Student zhouxingxing = new Student();
這行程式碼就建立了兩塊記憶體空間,第一個在棧中,名字叫zhouxingxing,它就相當於指標型別的變數,我們看到它並不存放學生的姓名、年齡等具體的數值,而是存放堆中第二塊記憶體的地址,第二塊才存放具體的數值,如學生的編號、姓名、年齡等資訊。
(3)int a = 10;
這是 基本資料型別 變數,具體的值就存放在棧中,並沒有只指標的概念!
下圖就是本例的記憶體佈置圖:
此外我們還要知道Student zhouxingxing = new Student(); 包括了宣告和建立,即:Student zhouxingxing;和zhouxingxing = new Student();其中宣告只是在棧中宣告一個空間,但還沒有具體的值,宣告後的情況如下圖所示:
建立後的情況如下圖所示:
(4)引用型別中的陣列也封裝了指標,即便是基本資料型別的陣列也封裝了指標,陣列也是引用型別。比如程式碼int[] arr = new int[]{23,2,4,3,1};如下圖所示:
五、java值傳參與引用引數
(1)引數根據呼叫後的效果不同,即是否改變引數的原始數值,又可以分為兩種:按值傳遞的引數與按引用傳遞的引數。
按值傳遞的引數原始數值不改變,按引用傳遞的引數原始數值改變!這是為什麼呢?其實相當簡單:
我們知道基本資料型別的變數存放在棧裡面,變數名處存放的就是變數的值,那麼當基本資料型別的變數作為引數時,傳遞的就是這個值,只是把變數的值傳遞了過去,不管對這個值如何操作,都不會改變變數的原始值。而對引用資料型別的變數來說,變數名處存放的地址,所以引用資料型別的變數作為傳參時,傳遞的實際上是地址,對地址處的內容進行操作,當然會改變變數的值了!
(2)特例:string
public class TestString { public static void main(String[] args) { String name = "wangwang"; TestString testString = new TestString(); System.out.println("方法呼叫前:" + name); testString.change(name); System.out.println("方法呼叫後:" + name); } void change(String str) { str = "旺旺老師"; System.out.println("方法體內修改值後:" + str); } }
結果:
方法呼叫前:wangwang
方法體內修改值後:旺旺老師
方法呼叫後:wangwang
分析:
上例中,雖然引數String 是引用資料型別,但其值沒有發生改變,這是因為String 類
是final 的,它是定長,我們看初始情況,即String name = "wangwang";這行程式碼執行
完,如下圖:
當呼叫方法時testString.change(name),記憶體變化為:
在方法體內,引數str賦予一個新值,str = "旺旺老師"。因為String是定長,系統就會在堆中分配一塊新的記憶體空間37DF,這樣str指向了新的記憶體空間37DF,而name還是指向36DF, 37DF的改變對它已沒影響:
最後,方法呼叫結束,str與37DF的記憶體空間消亡。Name的值依然為wangwang,並沒有改變。
所以String雖然是引用型別引數,但值依然不變:
public class TestChange { void change(Student stu1, Student stu2) { stu1.stuAge ++; stu2.stuAge ++; Student stu = stu1; stu1 = stu2; stu2 = stu; } public static void main(String[] args) { Student furong = new Student(); furong.stuName = "芙蓉姐姐"; furong.stuAge = 30; Student fengjie = new Student(); fengjie.stuName = "鳳姐"; fengjie.stuAge = 26; TestChange testChange = new TestChange(); testChange.change(furong, fengjie); System.out.println(furong.stuName); System.out.println(furong.stuAge); System.out.println(fengjie.stuName); System.out.println(fengjie.stuAge); } }
執行結果:
芙蓉姐姐
31
鳳姐
27
分析: