Java內存管理方法小結
這里向大家簡單介紹一下Java內存管理的概念和方法,Java內存管理就是對象的分配和釋放問題。首先看一下分配和釋放的概念,分配:內存的分配是由程序完成的,程序員需要通過關鍵字new為每個對象申請內存空間(基本類型除外),所有的對象都在堆(Heap)中分配空間;而對象的釋放是由垃圾回收機制決定和執行的。
Java內存管理總結
1.Java是如何管理內存的
Java內存管理就是對象的分配和釋放問題。
分配:內存的分配是由程序完成的,程序員需要通過關鍵字new為每個對象申請內存空間(基本類型除外),所有的對象都在堆(Heap)中分配空間。
釋放:對象的釋放是由垃圾回收機制決定和執行的,這樣做確實簡化了程序員的工作。但同時,它也加重了JVM的工作。這也是Java程序運行速度較慢的原因之一。因為,GC為了能夠正確釋放對象,GC必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。
2.什么叫Java的內存泄露
在Java內存管理中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連(也就是說仍存在該內存對象的引用);其次,這些對象是無用的,即程序以后不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,然而它卻占用內存。
與C++內存泄露的區別:
在C++中,內存泄漏的范圍更大一些。有些對象被分配了內存空間,然后卻不可達,由于C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,因此程序員不需要考慮這部分的內存泄露。
3.JVM的內存區域組成
Java把內存分兩種:一種是棧內存,另一種是堆內存
1。在函數中定義的基本類型變量和對象的引用變量都在函數的棧內存中分配;
2。堆內存用來存放由new創建的對象和數組以及對象的實例變量
在函數(代碼塊)中定義一個變量時,Java就在棧中為這個變量分配內存空間,當超過變量的作用域后,Java會自動釋放掉為該變量所分配的內存空間;在堆中分配的內存由Java虛擬機的自動垃圾回收器來管理
3。棧的優缺點
堆的優勢是可以動態分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的。缺點就是要在運行時動態分配內存,存取速度較慢;
棧的優勢是,存取速度比堆要快,僅次于直接位于CPU中的寄存器。另外,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。#p#
4。Java內存管理中數據如何存儲
a)基本數據類型
Java內存管理中的基本數據類型共有8種,即int,short,long,byte,float,double,boolean,char(注意,并沒有string的基本類型)。這種類型的定義是通過諸如inta=3;longb=255L;的形式來定義的。如inta=3;這里的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由于大小可知,生存期可知(這些字面值定義在某個程序塊里面,程序塊退出后,字段值就消失了),出于追求速度的原因,就存在于棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。
比如:我們同時定義:
inta=3;
intb=3;
編譯器先處理inta=3;首先它會在棧中創建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理intb=3;在創建完b這個引用變量后,由于在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。
定義完a與b的值后,再令a=4;那么,b不會等于4,還是等于3。在編譯器內部,遇到時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
b)對象
在Java內存管理中,創建一個對象包括對象的聲明和實例化兩步,下面用一個例題來說明對象的內存模型。
假設有類Rectangle定義如下:
Java代碼
- classRectangle{
- doublewidth,height;
- Rectangle(doublew,doubleh){
- wwidth=w;
- hheight=h;
- }
- }
- classRectangle{
- doublewidth,height;
- Rectangle(doublew,doubleh){
- wwidth=w;
- hheight=h;
- }
- }
(1)明對象時的內存模型
用Rectanglerect;聲明一個對象rect時,將在棧內存為對象的引用變量rect分配內存空間,但Rectangle的值為空,稱rect是一個空對象。空對象不能使用,因為它還沒有引用任何“實體”。
(2)對象實例化時的內存模型
當執行rect=newRectangle(3,5);時,會做兩件事:
在堆內存中為類的成員變量width,height分配內存,并將其初始化為各數據類型的默認值;接著進行顯式初始化(類定義時的初始化值);最后調用構造方法,為成員變量賦值。
返回堆內存中對象的引用(相當于首地址)給引用變量rect,以后就可以通過rect來引用堆內存中的對象了。
c)創建多個不同的對象實例
一個類通過使用new運算符可以創建多個不同的對象實例,這些對象實例將在堆中被分配不同的內存空間,改變其中一個對象的狀態不會影響其他對象的狀態。例如:
Java代碼
- Rectangler1=newRectangle(3,5);
- Rectangler2=newRectangle(4,6);
- Rectangler1=newRectangle(3,5);
- Rectangler2=newRectangle(4,6);
此時,將在堆內存中分別為兩個對象的成員變量width、height分配內存空間,兩個對象在堆內存中占據的空間是互不相同的。如果有:
Java代碼
- Rectangler1=newRectangle(3,5);
- Rectangler2=r1;
- Rectangler1=newRectangle(3,5);
- Rectangler2=r1;
則在堆內存中只創建了一個對象實例,在棧內存中創建了兩個對象引用,兩個對象引用同時指向一個對象實例。#p#
d)包裝類
Java內存管理中數據的基本型別都有對應的包裝類:如int對應Integer類,double對應Double類等,基本類型的定義都是直接在棧中,如果用包裝類來創建對象,就和普通對象一樣了。例如:inti=0;i直接存儲在棧中。Integeri(i此時是對象)=newInteger(5);這樣,i對象數據存儲在堆中,i的引用存儲在棧中,通過棧中的引用來操作對象。
e)String
String是一個特殊的包裝類數據。可以用用以下兩種方式創建:
- 1.Stringstr=newString("abc");
- 2.Stringstr="abc";
第一種創建方式,和普通對象的的創建過程一樣;
第二種創建方式,Java內部將此語句轉化為以下幾個步驟:
(1)先定義一個名為str的對String類的對象引用變量:Stringstr;
(2)在棧中查找有沒有存放值為“abc”的地址,如果沒有,則開辟一個存放字面值為“abc”的地址,接著創建一個新的String類的對象o,并將o的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。如果已經有了值為“abc”的地址,則查找對象o,并返回o的地址。
(3)將str指向對象o的地址。
值得注意的是,一般String類中字符串值都是直接存值的。但像Stringstr="abc";這種場合下,其字符串值卻是保存了一個指向存在棧中數據的引用。
為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。
Java代碼
- Stringstr1=“abc”;
- Stringstr2=“abc”;
- System.out.println(s1==s2);//true
- Stringstr1=“abc”;
- Stringstr2=“abc”;
- System.out.println(s1==s2);//true
注意,這里并不用str1.equals(str2);的方式,因為這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而我們在這里要看的是,str1與str2是否都指向了同一個對象。
我們再接著看以下的代碼。
Java代碼
- Stringstr1=newString(“abc”);
- Stringstr2=“abc”;
- System.out.println(str1==str2);//false
- Stringstr1=newString(“abc”);
- Stringstr2=“abc”;
- System.out.println(str1==str2);//false
創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。
以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創建,而且其字符串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享。
f)數組
當定義一個數組,intx[];或int[]x;時,在棧內存中創建一個數組引用,通過該引用(即數組名)來引用數組。x=newint[3];將在堆內存中分配3個保存int型數據的空間,堆內存的首地址放到棧內存中,每個數組元素被初始化為0。
g)靜態變量
用static的修飾的變量和方法,實際上是指定了這些變量和方法在內存中的“固定位置”-staticstorage,可以理解為所有實例對象共有的內存空間。static變量有點類似于C中的全局變量的概念;靜態表示的是內存的共享,就是它的每一個實例都指向同一個內存地址。把static拿來,就是告訴JVM它是靜態的,它的引用(含間接引用)都是指向同一個位置,在那個地方,你把它改了,它就不會變成原樣,你把它清理了,它就不會回來了。
那靜態變量與方法是在什么時候初始化的呢?對于兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance屬性在創建實例的時候初始化,static屬性在類加載,也就是第一次用到這個類的時候初始化,對于后來的實例的創建,不再次進行初始化。
我們常可看到類似以下的例子來說明這個問題:
Java代碼
- classStudent{
- staticintnumberOfStudents=0;
- Student()
- {
- numberOfStudents++;
- }
- }
- classStudent{
- staticintnumberOfStudents=0;
- Student()
- {
- numberOfStudents++;
- }
- }
每一次創建一個新的Student實例時,成員numberOfStudents都會不斷的遞增,并且所有的Student實例都訪問同一個numberOfStudents變量,實際上intnumberOfStudents變量在內存中只存儲在一個位置上。
【編輯推薦】
- 遠離陋習 Java內存管理的9個小技巧
- Java內存管理的幾個技巧
- JVM.dll裝載過程與源代碼分析
- 巧解使Eclipse崩潰的JVM terminated問題
- 解決JVM Terminated.ExitCode=-1問題行之有效的方法