1. 程式人生 > >java中的各種資料型別在記憶體中儲存的方式

java中的各種資料型別在記憶體中儲存的方式

1.java是如何管理記憶體的

java的記憶體管理就是物件的分配和釋放問題。(其中包括兩部分)

分配:記憶體的分配是由程式完成的,程式設計師需要通過關鍵字new為每個物件申請記憶體空間(基本型別除外),所有的物件都在堆(Heap)中分配空間。
釋放:物件的釋放是由垃圾回收機制決定和執行的,這樣做確實簡化了程式設計師的工作。但同時,它也加重了JVM的工作。因為,GC為了能夠正確釋放物件,GC必須監控每一個物件的執行狀態,包括物件的申請、引用、被引用、賦值等,GC都需要進行監控。

2.什麼叫java的記憶體洩露

在java中,記憶體洩漏就是存在一些被分配的物件,這些物件有下面兩個特點,首先,這些物件是可達的,即在有向圖中,存在通路可以與其相連(也就是說仍存在該記憶體物件的引用);其次,這些物件是無用的,即程式以後不會再使用這些物件。如果物件滿足這兩個條件,這些物件就可以判定為Java中的記憶體洩漏,這些物件不會被GC所回收,然而它卻佔用記憶體。

3.JVM的記憶體區域組成

java把記憶體分兩種:一種是棧記憶體,另一種是堆記憶體
(1)在函式中定義的基本型別變數和物件的引用變數都在函式的棧記憶體中分配;
(2)堆記憶體用來存放由new建立的物件和陣列以及物件的例項變數。在函式(程式碼塊)中定義一個變數時,java就在棧中為這個變數分配記憶體空間,當超過變數的作用域後,java會自動釋放掉為該變數所分配的記憶體空間;在堆中分配的記憶體由java虛擬機器的自動垃圾回收器來管理
堆和棧的優缺點

堆的優勢是可以動態分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在執行時動態分配記憶體的。

缺點就是要在執行時動態分配記憶體,存取速度較慢;棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的暫存器。

另外,棧資料可以共享。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。

4.java中資料在記憶體中是如何儲存的

a)基本資料型別

java的基本資料型別共有8種,即int,short,long,byte,float,double,boolean,char(注意,並沒有String的基本型別 )。這種型別的定義是通過諸如int a = 3;long b = 255L;的形式來定義的。如int a = 3;這裡的a是一個指向int型別的引用,指向3這個字面值。這些字面值的資料,由於大小可知,生存期可知(這些字面值定義在某個程式塊裡面,程式塊退出後,欄位值就消失了),出於追求速度的原因,就存在於棧中。

另外,棧有一個很重要的特殊性,就是存在棧中的資料可以共享。比如:
我們同時定義:

int a=3;
int b=3;

編譯器先處理int a = 3;首先它會在棧中建立一個變數為a的引用,然後查詢有沒有字面值為3的地址,沒找到,就開闢一個存放3這個字面值的地址,然後將a指向3的地址。接著處理int b = 3;在建立完b這個引用變數後,由於在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。

定義完a與b的值後,再令a = 4;那麼,b不會等於4,還是等於3。在編譯器內部,遇到時,它就會重新搜尋棧中是否有4的字面值,如果沒有,重新開闢地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。

b)物件

在java中,建立一個物件包括物件的宣告和例項化兩步,下面用一個例題來說明物件的記憶體模型。假設有類Rectangle定義如下:

  1. publicclassRectangle{
  2. double width;
  3. double height;
  4. publicRectangle(double w,double h){
  5. w = width;
  6. h = height;
  7. }
  8. }

(1)宣告物件時的記憶體模型
用Rectangle rect;宣告一個物件rect時,將在棧記憶體為物件的引用變數rect分配記憶體空間,但Rectangle的值為空,稱rect是一個空物件。空物件不能使用,因為它還沒有引用任何”實體”。
(2)物件例項化時的記憶體模型
當執行rect=new Rectangle(3,5);時,會做兩件事:在堆記憶體中為類的成員變數width,height分配記憶體,並將其初始化為各資料型別的預設值;接著進行顯式初始化(類定義時的初始化值);最後呼叫構造方法,為成員變數賦值。返回堆記憶體中物件的引用(相當於首地址)給引用變數rect,以後就可以通過rect來引用堆記憶體中的物件了。

c)建立多個不同的物件例項

一個類通過使用new運算子可以建立多個不同的物件例項,這些物件例項將在堆中被分配不同的記憶體空間,改變其中一個物件的狀態不會影響其他物件的狀態。例如:

  1. Rectangle r1=newRectangle(3,5);
  2. Rectangle r2=newRectangle(4,6);

此時,將在堆記憶體中分別為兩個物件的成員變數 width 、 height 分配記憶體空間,兩個物件在堆記憶體中佔據的空間是互不相同的。如果有:

  1. Rectangle r1=newRectangle(3,5);
  2. Rectangle r2=r1;

則在堆記憶體中只建立了一個物件例項,在棧記憶體中建立了兩個物件引用,兩個物件引用同時指向一個物件例項。

d)包裝類

基本型別都有對應的包裝類:如int對應Integer類,double對應Double類等,基本型別的定義都是直接在棧中,如果用包裝類來建立物件,就和普通物件一樣了。例如:int i=0;i直接儲存在棧中。Integer i(i此時是物件)= new Integer(5);這樣,i物件資料儲存在堆中,i的引用儲存在棧中,通過棧中的引用來操作物件。

e)String

String是一個特殊的包裝類資料。可以用以下兩種方式建立:String str = new String(“abc”);String str = “abc”;
第一種建立方式,和普通物件的的建立過程一樣;
第二種建立方式,java內部將此語句轉化為以下幾個步驟:
(1)先定義一個名為str的對String類的物件引用變數:String str;
(2)在棧中查詢有沒有存放值為”abc”的地址,如果沒有,則開闢一個存放字面值為”abc”
地址,接著建立一個新的String類的物件o,並將o的字串值指向這個地址,而且在棧
這個地址旁邊記下這個引用的物件o。如果已經有了值為”abc”的地址,則查詢物件o,並
回o的地址。
(3)將str指向物件o的地址。
值得注意的是,一般String類中字串值都是直接存值的。但像String str = “abc”;這種
合下,其字串值卻是儲存了一個指向存在棧中資料的引用。
為了更好地說明這個問題,我們可以通過以下的幾個程式碼進行驗證。

  1. String str1="abc"
  2. String str2="abc"
  3. System.out.println(s1==s2);//true

注意,這裡並不用 str1.equals(str2);的方式,因為這將比較兩個字串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個物件時才返回真值。而我們在這裡要看的是,str1與str2是否都指向了同一個物件。
我們再接著看以下的程式碼。

  1. String str1=newString("abc");
  2. String str2="abc"
  3. System.out.println(str1==str2);//false

建立了兩個引用。建立了兩個物件。兩個引用分別指向不同的兩個物件。
以上兩段程式碼說明,只要是用new()來新建物件的,都會在堆中建立,而且其字串是單獨存值的,即使與棧中的資料相同,也不會與棧中的資料共享。

f)陣列

當定義一個數組,int x[];或int[] x;時,在棧記憶體中建立一個數組引用,通過該引用(即陣列名)來引用陣列。x=new int[3];將在堆記憶體中分配3個儲存 int型資料的空間,堆記憶體的首地址放到棧記憶體中,每個陣列元素被初始化為0。

g)靜態變數

用static的修飾的變數和方法,實際上是指定了這些變數和方法在記憶體中的”固定位置”-static storage,可以理解為所有例項物件共有的記憶體空間。static變數有點類似於C中的全域性變數的概念;靜態表示的是記憶體的共享,就是它的每一個例項都指向同一個記憶體地址。把static拿來,就是告訴JVM它是靜態的,它的引用(含間接引用)都是指向同一個位置,在那個地方,你把它改了,它就不會變成原樣,你把它清理了,它就不會回來了。

那靜態變數與方法是在什麼時候初始化的呢?對於兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance屬性在建立例項的時候初始化,static屬性在類載入,也就是第一次用到這個類的時候初始化,對於後來的例項的建立,不再次進行初始化。

我們常可看到類似以下的例子來說明這個問題:

  1. classStudent{
  2. staticint numberOfStudents =0;
  3. Student()
  4. {
  5. numberOfStudents ++;
  6. }
  7. }

每一次建立一個新的Student例項時,成員numberOfStudents都會不斷的遞增,並且所有的Student例項都訪問同一個numberOfStudents變數,實際上intnumberOfStudents變數在記憶體中只儲存在一個位置上。

5.java的記憶體管理例項

Java程式的多個部分(方法,變數,物件)駐留在記憶體中以下兩個位置:即堆和棧,現在我們只關心三類事物:例項變數,區域性變數和物件:
例項變數和物件駐留在堆上
區域性變數駐留在棧上
讓我們檢視一個 java 程式,看看他的各部分如何建立並且對映到棧和堆中:

  1. publicclassDog{
  2. Collar c;
  3. String name;
  4. //1.main()方法位於棧上
  5. publicstaticvoid main(String[] args){
  6. //2.在棧上建立引用變數d,但Dog物件尚未存在
  7. Dog d;
  8. //3.建立新的Dog物件,並將其賦予d引用變數
  9. d =newDog();
  10. //4.將引用變數的一個副本傳遞給go()方法
  11. d.go(d);
  12. }
  13. //5.將go()方法置於棧上,並將dog引數作為區域性變數
  14. void go(Dog dog){
  15. //6.在堆上建立新的Collar物件,並將其賦予Dog的例項變數
  16. c =newCollar();
  17. }
  18. //7.將setName()新增到棧上,並將dogName引數作為其區域性變數
  19. void setName(String dogName){
  20. //8.name的例項物件也引用String物件
  21. name =dogName;
  22. }
  23. //9.程式執行完成後,setName()將會完成並從棧中清除,此時,區域性變數dogName也會消失,儘管它所引用的String仍在堆上
  24. }

6. 垃圾回收機制

問題一:什麼叫垃圾回收機制?
垃圾回收是一種動態儲存管理技術,它自動地釋放不再被程式引用的物件,按照特定的垃圾收集演算法來實現資源自動回收的功能。當一個物件不再被引用的時候,記憶體回收它佔領的空間,以便空間被後來的新物件使用,以免造成記憶體洩露。

問題二:java的垃圾回收有什麼特點?
jAVA語言不允許程式設計師直接控制記憶體空間的使用。記憶體空間的分配和回收都是由JRE負責在後臺自動進行的,尤其是無用記憶體空間的回收操作(garbagecollection,也稱垃圾回收),只能由執行環境提供的一個超級執行緒進行監測和控制。

問題三:垃圾回收器什麼時候會執行?
一般是在CPU空閒或空間不足時自動進行垃圾回收,而程式設計師無法精確控制垃圾回收的時機和順序等。、

問題四:什麼樣的物件符合垃圾回收條件?
當沒有任何獲得執行緒能訪問一個物件時,該物件就符合垃圾回收條件。

問題五:垃圾回收器是怎樣工作的?
垃圾回收器如發現一個物件不能被任何活執行緒訪問時,他將認為該物件符合刪除條件,就將其加入回收佇列,但不是立即銷燬物件,何時銷燬並釋放記憶體是無法預知的。垃圾回收不能強制執行,然而java提供了一些方法(如:System.gc()方法),允許你請求JVM執行垃圾回收,而不是要求,虛擬機器會盡其所能滿足請求,但是不能保證JVM從記憶體中刪除所有不用的物件。

問題六:一個java程式能夠耗盡記憶體嗎?
可以。垃圾收集系統嘗試在物件不被使用時把他們從記憶體中刪除。然而,如果保持太多活的物件,系統則可能會耗盡記憶體。垃圾回收器不能保證有足夠的記憶體,只能保證可用記憶體儘可能的得到高效的管理。

問題七:如何顯示的使物件符合垃圾回收條件?
(1)空引用:當物件沒有對他可到達引用時,他就符合垃圾回收的條件。也就是說如果沒有對他的引用,刪除物件的引用就可以達到目的,因此我們可以把引用變數設定為null,來符合垃圾回收的條件。

  1. StringBuffer sb =newStringBuffer("hello");
  2. System.out.println(sb);
  3. sb=null;

(2)重新為引用變數賦值:可以通過設定引用變數引用另一個物件來解除該引用變數與一個物件間的引用關係。
StringBuffer sb1 = new StringBuffer(“hello”);
StringBuffer sb2 = new StringBuffer(“goodbye”);
System.out.println(sb1);
sb1=sb2;//此時”hello”符合回收條件
(3)方法內建立的物件:所建立的區域性變數僅在該方法的作用期間記憶體在。一旦該方法返回,在這個方法內建立的物件就符合垃圾收集條件。有一種明顯的例外情況,就是方法的返回物件。

  1. publicstaticvoid main(String[] args){
  2. Date d = getDate();
  3. System.out.println("d="+d);
  4. }
  5. privatestaticDate getDate(){
  6. Date d2 =newDate();
  7. StringBuffer now =newStringBuffer(d2.toString());
  8. System.out.println(now);
  9. return d2;
  10. }

(4)隔離引用:這種情況中,被回收的物件仍具有引用,這種情況稱作隔離島。若存在這兩個例項,他們互相引用,並且這兩個物件的所有其他引用都刪除,其他任何執行緒無法訪問這兩個物件中的任意一個。也可以符合垃圾回收條件。

  1. publicclassIsland{
  2. Island i;
  3. publicstaticvoid main(String[] args){
  4. Island i2 =newIsland();
  5. Island i3 =newIsland();
  6. Island i4 =newIsland();
  7. i2. i =i3;
  8. i3. i =i4;
  9. i4. i =i2;
  10. i2=null;
  11. i3=null;
  12. i4=null;
  13. }
  14. }

問題八:垃圾收集前進行清理——finalize()方法
java提供了一種機制,使你能夠在物件剛要被垃圾回收之前執行一些程式碼。這段程式碼位於名為finalize()的方法內,所有類從Object類繼承這個方法。由於不能保證垃圾回收器會刪除某個物件。因此放在finalize()中的程式碼無法保證執行。因此建議不要重寫finalize();

7.final問題

final使得被修飾的變數”不變”,但是由於物件型變數的本質是”引用”,使得”不變”也有了兩種含義:引用本身的不變和引用指向的物件不變。
引用本身的不變:

  1. finalStringBuffer a=newStringBuffer("immutable");
  2. finalStringBuffer b=newStringBuffer("not immutable");
  3. a=b;//編譯期錯誤
  4. finalStringBuffer a=newStringBuffer("immutable");
  5. finalStringBuffer b=newStringBuffer("not immutable");

a=b;//編譯期錯誤

引用指向的物件不變:

  1. finalStringBuffer a=newStringBuffer("immutable");
  2. a.append"broken!");//編譯通過
  3. finalStringBuffer a=newStringBuffer("immutable");
  4. a.append("broken!");//編譯通過

可見,final只對引用的”值”(也即它所指向的那個物件的記憶體地址)有效,它迫使引用只能指向初始指向的那個物件,改變它的指向會導致編譯期錯誤。至於它所指向的物件的變化,final是不負責的。這很類似==操作符:==操作符只負責引用的”值”相等,至於這個地址所指向的物件內容是否相等,==操作符是不管的。在舉一個例子:

  1. publicclassName{
  2. privateString firstname;
  3. privateString lastname;
  4. publicString getFirstname(){
  5. return firstname;
  6. }
  7. publicvoid setFirstname(String firstname){
  8. this.firstname = firstname;
  9. }
  10. publicString getLastname(){
  11. return lastname;
  12. }
  13. publicvoid setLastname(String lastname){
  14. this.lastname = lastname;
  15. }
  16. }
  17. publicclassName{
  18. privateString firstname;
  19. privateString lastname;
  20. publicString getFirstname(){
  21. 相關推薦

    Java各種資料型別儲存-堆/棧

    Java的記憶體分兩種:棧記憶體和堆記憶體 棧記憶體:在函式中定義的基本型別變數和引用變數都在棧記憶體中分配 例如: public void test(){ int a = 1; Person p = new Person();

    python各種資料型別

    數字型別 整型int   作用:年紀,等級,身份證號,qq號等與整型數字有關   定義:   age=10 #本質age=int(10) 浮點型float   作用:薪資,身高,體重等與浮點數相關 salary=3.1#本質salary=float(3.1) 該型別總結

    redis各種資料型別對應的jedis操作命令

    一、常用資料型別簡介:       redis常用五種資料型別:string,hash,list,set,zset(sorted set). 1.String型別 String是最簡單的型別,一個key對應一個value String型別的資料最大1G。 String

    C/C++各種 資料型別、結構體、類 佔用位元組數分析與總結

    一、基本資料型別在不同編譯器下佔用位元組數比較與總結,測試過程不詳述了,直接看下錶結論! 下表中右側總結部分是依據佔用位元組數進行著色,同一種顏色型別的資料成員佔用位元組數要麼一致,要麼具有同樣的性質,這樣比較容易理解的記憶。 佔用位元組數

    C語言各種資料型別的大小

    C語言   1.幾條規則   (1)char型別一般是8bit,但ANSI C裡沒有硬性規定其長度,某些嵌入式編譯器可能是16bit   (2)short和long型別的長度不相同   (3)int型別通常同具體機器的物理字長相同   (4)short通常是16bits, 

    C語言各種資料型別長度

    C語言的基本資料型別有以下幾個: int 整型 char 字元型 float 單精度浮點型 double 雙精度浮點型 另外,在基本資料型別基礎上附加一些限定詞, 得到擴充的資料型別。short,long可以應用到整型, unsigned可以應用到整型和字元型: short int(或short) 短整型 l

    在C語言各種資料型別各佔多少位

    (一)32位平臺:分為有符號型與無符號型。有符號型:short 在記憶體中佔兩個位元組,範圍為-2^15~(2^15-1)int 在記憶體中佔四個位元組,範圍為-2^31~(2^31-1)long在記憶體中佔四個位元組,範圍為-2^31~2^31-1無符號型:最高位不表示符號位unsigned short 在

    C語言各種資料型別所佔的位元組和取值範圍

    問題:C語言資料型別取值範圍,是根據什麼定義這個範圍取值? 首先,在計算機中所有資料都是用一個一個的二進位制位(0或1)儲存的,單位稱為:位(bit);然後,每8位二進位制數(比如01010001)代表一個位元組(byte)大小,即1位元組=8位;再然後,C語言每個資料型別

    android各種資料型別之間轉換

    字串String轉換成整數int 1). int i = Integer.parseInt([String]); 或 i = Integer.parseInt([String],[int rad

    js檢測資料型別的幾種方式

    1、typeof 一元運算子,用來檢測資料型別。只可以檢測number,string,boolean,object,function,undefined。 對於基本資料型別是沒有問題的,但是遇到引用資料型別是不起作用的(無法細分物件)

    詳解C++/C資料記憶體的存取

    本文介紹了指標的一個使用技巧,可以通過此方法將微控制器中的變數按位元組儲存到EEPROM中,也可以從EEPROM中還原一個多位元組的整型數或者浮點數。 在記憶體中變數對整數型別,包括 char, unsigned char, int, unsigned int, lon

    java各種資料型別記憶體儲存方式

    1.java是如何管理記憶體的 java的記憶體管理就是物件的分配和釋放問題。(其中包括兩部分) 分配:記憶體的分配是由程式完成的,程式設計師需要通過關鍵字new為每個物件申請記憶體空間(基本型別除外),所有的物件都在堆(Heap)中分配空間。 釋放:物件的釋放是由

    JAVA基本資料型別和佔用記憶體空間大小

    byte:1個位元組,8位,-128~127 最大儲存資料量 255short:2個位元組,16位,-32768~32767 最大儲存資料量 65535char:2個位元組,16位,儲存Unicode編碼,用‘’int:4個位元組,32位,負的2的31次方~2的31次方-1 

    java資料記憶體儲存詳解

      1. 有這樣一種說法,如今爭鋒於IT戰場的兩大勢力,MS一族偏重於底層實現,Java一族偏重於系統架構。說法根據無從考證,但從兩大勢力各自的社群力量和圖書市場已有佳作不難看出,此說法不虛,但掌握Java的底層實現對Java程式設計師來說是至關重要的,本文介紹了Java中的資料在記憶體中的儲存。   

    float型別資料記憶體如何儲存的?

    float型別數字在計算機中用4個位元組儲存。遵循IEEE-754格式標準:一個浮點數有2部分組成:底數m和指數e 底數部分 使用二進位制數來表示此浮點數的實際值 指數部分 佔用8bit的二進位制數,可表示數值範圍為0-255 但是指數可正可負,所以,IEEE規定,此處算出的次方必須減去127才是真正的指數。

    float:double型別資料記憶體儲存格式

    float/double型別資料在記憶體中中儲存格式 float/double型別資料在計算機是如何儲存的呢? 它們是ieee standard 754的儲存方式。 譬如float數,第一位是符號位,然後是8位指數位,然後是23位尾數;double雙

    JAVA的jdbc向MySql表插入各種資料型別

    pStmt=conn.prepareStatement("INSERT INTO RH_ENTRY"+ "(id,allow_comments,category_id,comment_count,content,create_on,name,status)"+ "values(?,?,?,?,?,?,?,?)

    sizeof運算子來獲取各種資料型別記憶體所佔位元組數--gyy整理

    C++並沒有規定各種資料型別在記憶體中的儲存大小,依賴於不同的編譯器的不同而不同,要想獲知當前編譯器對各種資料型別分配的大小,可以通過sizeof運算子來獲取。 使用方法1: sizeof(資料型別)   使用方法2: sizeof(變數名   或 常量名 或 表示式  )

    Java語言資料型別

    1.Java語言中的資料型別 為什麼要使用資料型別呢,因為我們要編寫程式,要編寫程式就要建立變數,而建立變數就需要資料型別。 建立變數時使用資料型別就可以指定,自己建立的變數在程式執行時所佔用的記憶體空間大小。 2.變數 任何一個程式中都有變數存在。 變數就是程式執行過程中隨時可能會發生變

    java資料型別及轉換形式

       java中的資料型別分為兩種:基本資料型別和引用資料型別;    基本資料型別中包含的就是我們常說的“四類八種”,即 整數型:byte、short、int、long,浮點型:float、double,布林型:boolean,字元型:char 4類8種