1. 程式人生 > >【Java程式設計思想閱讀筆記】Java資料儲存位置

【Java程式設計思想閱讀筆記】Java資料儲存位置

Java資料儲存位置

P46頁有感

一、前置知識

  • 棧是由系統自動分配的,Java程式設計師對棧沒有直接的操作許可權,
  • 堆是所有執行緒共享的記憶體區域,棧 是每個執行緒獨享的。
  • 堆是由程式設計師自己申請的,在使用new關鍵字建立一個物件的時候,物件就會被分配到堆記憶體中。並且由於棧是由系統自動分配的,因此申請的效率和速度是高於要使用new關鍵字申請記憶體的堆。
  • 棧是一塊連續的區域,並且棧的大小系統確定好的,當需要的棧空間小於剩餘空間時,系統就會自動分配,否則會報棧溢位的錯誤;而堆空間則不是一塊連續的區域,如果學過作業系統課程的可以知道,作業系統中有一個記錄空閒記憶體地址的連結串列,當在申請堆記憶體時,系統會遍歷這個連結串列,找到第一個記憶體空間符合申請空間的節點地址,然後將該節點從空閒連結串列中刪除,再把記憶體空間分配給程式。詳細的分配過程,可以去學習作業系統課程。

二、Java的六個儲存位置

2.1.暫存器

暫存器位於CPU內部,這是計算機硬體中處理速度最快的地方,是真正的處理程式的位置。暫存器是由編譯器根據需要來分配的,並且即便是編譯器,也需要通過作業系統的搶佔式排程才能夠獲得CPU的處理權,因此,暫存器的儲存空間時非常有限的,開發人員對暫存器沒有直接的控制權,在程式裡也沒法對暫存器有什麼操作。

2.2.堆疊

堆疊一般也叫棧,一般指的是RAM區域,處理速度僅次於暫存器。在堆疊中,一般存放的都是物件的引用,當棧指標下移的時候,就建立新的記憶體,反之則釋放記憶體。當建立一個物件時。該物件的會被放到堆記憶體的空間,而該物件的引用則會被放到棧記憶體中。並且Java 編譯器必須準確地知道堆疊內儲存的所有資料的“長度”以及“存
在時間”。以便回收空間和知道棧記憶體還剩餘多少空間。

2.3.堆

堆記憶體也是在RAM區域,它是物件的真正存放位置,當new一個物件時,該物件就會被存放至堆記憶體,並且Java編譯器不需要去在意堆中還剩多少空間,Java 有一個特別 的“垃圾收集器”,它會查詢用new建立的所有物件,並辨別其中哪些不再被引用。隨後,它會自動釋放由 那些閒置物件佔據的記憶體,以便能由新物件使用。這意味著我們根本不必操心記憶體的回收問題。只需簡單地建立物件,一旦不再需要它們,它們就會自動離去。這樣做可防止在C++裡很常見的一個程式設計問題:由於程式設計師忘記釋放記憶體造成的“記憶體溢位”,物件的銷燬---物件的引用放在棧中,所以使用完引用就被從棧中銷燬了,但是實際的物件仍然存放在堆中,只有在沒有任何的引用使用它的時候才被垃圾回收器銷燬掉。

class StaticTest { 
Static int i = 47; 
}
//現在我們 new兩個物件
StaticTest st1 = new StaticTest(); 
StaticTest st2 = new StaticTest();
//st1.i和st2.i都是47 儘管我們製作了兩個StaticTest物件,但它們仍然只佔據StaticTest.i的一個儲存空間。這兩個物件都共享同樣的i 此時不管是哪個物件執行st1++,另一個物件的i也會加一

2.5.常量池

常數儲存。常數值通常直接置於程式程式碼內部。這樣做是安全的,因為它們永遠都不會改變。有的常數 需要嚴格地保護,所以可考慮將它們置入ROM。

2.6 非RAM儲存

非RAM儲存。若資料完全獨立於一個程式之外,則程式不執行時仍可存在,並在程式的控制範圍之外。 其中兩個最主要的例子便是“流式物件”和“固定物件”。對於流式物件,物件會變成位元組流,通常會發給 另一臺機器。而對於固定物件,物件儲存在磁碟中。即使程式中止執行,它們仍可保持自己的狀態不變。對 於這些型別的資料儲存,一個特別有用的技巧就是它們能存在於其他媒體中。一旦需要,甚至能將它們恢復 成普通的、基於RAM的物件。Java 1.1提供了對Lightweight persistence的支援。未來的版本甚至可能提 供更完整的方案。

三、基本型別的儲存位置

在Java程式設計思想中對於基本資料型別的儲存位置有這麼一句話

對於這些基本型別,Java 採納了與C和C++相同的方法。也就是說,不是用 new建立變數,而是建立一個並非引用的“自動”變數。這個變數容納了具體的值,並置於堆疊中,能夠更 高效地存取。

但是我百度查找了一些相關資料,也有說基本資料型別有放在堆中的。梳理一下,首先,八大基本型別都不能看做物件,基本型別的儲存位置要分情況,當基本型別的建立是在類中。像這樣

class Demo{
    int a = 1;//情況1
    public int test(){
        int b = 2;//情況2
    }
}

在情況1下,建立的是全域性變數。是隨著物件一起存放在堆中的,在情況2下,建立的是區域性變數,每當程式呼叫方法時,系統都會為該方法建立一個方法棧,其所在方法中宣告的變數就放在方法棧中,當方法結束系統會釋放方法棧,其對應在該方法中宣告的變數隨著棧的銷燬而結束,這就區域性變數只能在方法中有效的原因。由此可見,基本型別並不是完全儲存在棧區的。原因是因為,上面基礎知識提到的,堆是所有執行緒共享的記憶體區域,棧 是每個執行緒獨享的。如果你將一個例項變數放在棧內,那麼就不存在多個執行緒訪問同一個物件資源了,這顯然是不對的,所以全域性變數要在堆上建立,也不是執行緒安全的。但是對於區域性變數,是在棧上建立的,每一次方法呼叫建立一個方法棧,獨享一份記憶體區域,其他的執行緒是不會訪問到該執行緒的資源,在 棧上建立也會減輕GC的壓力,隨著該方法的結束,相對應的方法棧記憶體消除,這種區域性變數佔用的記憶體自然就消失了,因此區域性變數是執行緒安全的