Java 程序運行時,需要在內存中分配空間。為了提高運算效率,就對空間進行了不同區域的劃分,因為每一片區域都有特定的處理數據方式和內存管理方式。
分配:通過關鍵字new創建對象分配內存空間,對象存在堆中。
釋放 :對象的釋放是由垃圾回收機制決定和執行的
JVM的內存可分為3個區:
堆(heap)
棧(stack)
方法區(method,也叫靜態區)
?
堆區: 程序員自己申請,運行時線程公有
通過new生成的對象都存放在堆中,對于堆中的對象生命周期的管理由Java虛擬機的垃圾回收機制GC進行回收和統一管理
jvm只有一個堆區(heap),且被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身和數組本身;
優點是可以動態分配內存大小,缺點是由于動態分配內存導致存取速度慢。
虛擬機棧: 系統自己分配,運行時線程私有
棧內存主要是存放一些基本類型的變量和對象的引用變量。最典型的就是我們new一個對象時,對象名作為變量就存放在棧內存中
棧內存有一個很重要的特殊性——在棧中的數據可以共享(已存在的值不會再次創建)
int a = 3;
int b = 3;
(編譯器先處理int a =3;首先它會在棧中創建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b)
每個方法執行的同時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等。局部變量表存放了編譯器可知的各種基本數據類型、對象引用和returnAddress類型
每個方法從調用直至完成的過程,都對應著一個棧幀從入棧到出棧的過程。每當一個方法執行完成時,該棧幀就會彈出棧幀的元素作為這個方法的返回值,并且清除這個棧幀,Java棧的棧頂的棧幀就是當前正在執行的活動棧,也就是當前正在執行的方法,方法的調用過程也是由棧幀切換來產生結果。
在JVM規范中,對這個區域規定了兩種異常情況:
1.如果線程請求的棧深度大于虛擬機允許的深度,將拋出StackOverflowError異常;
2.如果虛擬機棧可以動態擴展,在擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。
本地方法棧:
和虛擬機棧所發揮的作用基本一致,不同的是:虛擬機棧為虛擬機執行Java方法(字節碼)服務
本地方法棧則為虛擬機使用到的Native方法服務
本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常。
方法區(靜態區):
是各個線程共享的內存區域,它用于存儲class二進制文件,包含所有的class和static變量,包含了虛擬機加載的類信息、常量(常量池)、靜態變量(靜態域)、即時編譯后的代碼等數據。
方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量。
被所有的線程共享,方法區包含所有的class(class是指類的原始代碼,要創建一個類的對象,首先要把該類的代碼加載到方法區中,并且初始化)和static變量。
常量池:在編譯期間就將一部分數據存放于該區域,包含以final修飾的基本數據類型的常量值、String字符串。(在java6時它是方法區的一部分;1.7又把他放到了堆內存中;1.8之后出現了元空間,它又回到了方法區。)
靜態域:存放類中以static聲明的靜態成員變量。
程序計數器:當前線程所執行的行號指示器。通過改變計數器的值來確定下一條指令,比如循環,分支,跳轉,異常處理,線程恢復等都是依賴計數器來完成。
程序計數器:
一個非常小的內存空間,用來保存程序執行到的位置(線程私有),它可以看作是當前線程所執行的字節碼的行號指示器。
Metaspace元空間:
在JDK1.8中,永久代已經不存在,存儲的類信息、編譯后的代碼數據等已經移動到了MetaSpace(元空間)中,元空間并沒有處于堆內存上,而是直接占用的本地內存(NativeMemory)。
元空間的本質和永久代類似,都是對JVM規范中方法區的實現。
元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用本地內存。
元空間的大小僅受本地內存限制,可以指定元空間大小。
Java8為什么要將永久代替換成Metaspace?
1、字符串存在永久代中,容易出現性能問題和內存溢出。
2、類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
3、永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低。
4、Oracle 可能會將HotSpot 與 JRockit 合二為一。
?基本數據類型
int a = 3; int b = 3;
編譯器先處理 int a = 3;首先它會在棧中創建一個變量為 a 的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將 a 指向3的地址。
接著處理 int b = 3;在創建完 b 這個引用變量后,由于在棧中已經有3這個字面值,便將 b 直接指向3的地址。這樣,就出現了 a 與 b 同時均指向3的情況。
對象
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }
一個類通過使用new運算符可以創建多個不同的對象實例,這些對象實例將在堆中被分配不同的內存空間,改變其中一個對象的狀態不會影響其他對象的狀態:
? ? ? ? Person one = new Person("小明",15);Person two = new Person("小王",17);
在堆內存中只創建了一個對象實例,在棧內存中創建了兩個對象引用,兩個對象引用同時指向一個對象實例。
? ? ? ? Person one = new Person("小明",15);Person two = one;
數組
數組是一種引用類型,數組用來存儲同一種數據類型的數據。一旦初始化完成,即所占的空間就已固定下來,即使某個元素被清空,但其所在空間仍然保留,因此數組長度將不能被改變。
int[] array = new int[5]
首先會在棧中創建引用變量,在堆中開辟5個int型數據的空間,該引用變量存放數組首地址,即實現數組名來引用數組。
?