Java內存管理
首先我們要明白一點,我們所使用的變量就是一塊一塊的內存空間!!
一、內存管理原理:
在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雖然是引用類型參數,但值依然不變:
(3)無法交換的例子:
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
分析: