1、Java內存分配
一、 Java有幾種存儲區域?
* 寄存器
-- 在CPU內部,開發人員不能經過代碼來控制寄存器的分配,由編譯器來管理
* 棧
-- 在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存的區域,即棧頂的地址和棧的最大容量是系統預先規定好的。
-- 優勢:由系統自動分配,速度較快。
-- 缺點:不夠靈活,但程序員是沒法控制的。
-- 存放基本數據類型、開發過程當中就建立的對象(而不是運行過程當中)
* 堆
-- 是向高地址擴展的數據結構,是不連續的內存區域
-- 在堆中,沒有堆棧指針,爲此也就沒法直接從處理器那邊得到支持
-- 堆的好處是有很大的靈活性。如Java編譯器不須要知道從堆裏須要分配多少存儲區域,也沒必要知道存儲的數據在堆裏會存活多長時間。
* 靜態存儲區域與常量存儲區域
-- 靜態存儲區用來存放static類型的變量
-- 常量存儲區用來存放常量類型(final)類型的值,通常在只讀存儲器中
* 非RAM存儲
-- 如流對象,是要發送到另一臺機器上的
-- 持久化的對象,存放在磁盤上
二、 java內存分配
-- 基礎數據類型直接在棧空間分配;
-- 方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收;
-- 引用數據類型,須要用new來建立,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量;
-- 方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完後從棧空間回收;
-- 局部變量 new 出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間馬上被回收,堆空間區域等待GC回收;
-- 方法調用時傳入的 literal 參數,先在棧空間分配,在方法調用完成後從棧空間釋放;
-- 字符串常量在 DATA 區域分配 ,this 在堆空間分配;
-- 數組既在棧空間分配數組名稱, 又在堆空間分配數組實際的大小!
三、Java內存模型
* Java虛擬機將其管轄的內存大體分三個邏輯部分:方法區(Method Area)、Java棧和Java堆。
-- 方法區是靜態分配的,編譯器將變量在綁定在某個存儲位置上,並且這些綁定不會在運行時改變。
常數池,源代碼中的命名常量、String常量和static 變量保存在方法區。
-- Java Stack是一個邏輯概念,特色是後進先出。一個棧的空間多是連續的,也多是不連續的。
最典型的Stack應用是方法的調用,Java虛擬機每調用一次方法就建立一個方法幀(frame),退出該方法則對應的 方法幀被彈出(pop)。棧中存儲的數據也是運行時肯定的?
-- Java堆分配(heap allocation)意味着以隨意的順序,在運行時進行存儲空間分配和收回的內存管理模型。
堆中存儲的數據經常是大小、數量和生命期在編譯時沒法肯定的。Java對象的內存老是在heap中分配。
四、Java內存分配實例解析
常量池(constant pool)指的是在編譯期被肯定,並被保存在已編譯的.class文件中的一些數據。它包括了關於類、方法、接口等中的常量,也包括字符串常量。
常 量池在運行期被JVM裝載,而且能夠擴充。String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用 intern()方法時,Java查找常量池中是否有相同Unicode的字符串常量,若是有,則返回其引用,若是沒有,則在常量池中增長一個 Unicode等於str的字符串並返回它的引用。
例:
String s1=new String("kvill");
String s2=s1.intern();
System.out.println( s1==s1.intern() );//false
System.out.println( s1+" "+s2 );// kvill kvill
System.out.println( s2==s1.intern() );//true
這個類中事先沒有聲名」kvill」常量,因此常量池中一開始是沒有」kvill」的,當調用s1.intern()後就在常量池中新添加了一 個」kvill」常量,原來的不在常量池中的」kvill」仍然存在。s1==s1.intern()爲false說明原來的「kvill」仍然存 在;s2如今爲常量池中「kvill」的地址,因此有s2==s1.intern()爲true。程序員
String 常量池問題
(1) 字符串常量的"+"號鏈接,在編譯期字符串常量的值就肯定下來, 拿"a" + 1來講,編譯器優化後在class中就已是a1。
String a = "a1";
String b = "a" + 1;
System.out.println((a == b)); //result = true
String a = "atrue";
String b = "a" + "true";
System.out.println((a == b)); //result = true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println((a == b)); //result = true
(2) 對於含有字符串引用的"+"鏈接,沒法被編譯器優化。
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false
因爲引用的值在程序編譯期是沒法肯定的,即"a" + bb,只有在運行期來動態分配並將鏈接後的新地址賦給b。
(3) 對於final修飾的變量,它在編譯時被解析爲常量值的一個本地拷貝並存儲到本身的常量池中或嵌入到它的字節碼流中。因此此時的"a" + bb和"a" + "b"效果是同樣的。
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true
(4) jvm對於字符串引用bb,它的值在編譯期沒法肯定,只有在程序運行期調用方法後,將方法的返回值和"a"來動態鏈接並分配地址爲b。
String a = "ab";
final String bb = getbb();
String b = "a" + bb;
System.out.println((a == b)); //result = false
private static string getbb() {
return "b";
}
(5) String 變量採用鏈接運算符(+)效率低下。
String s = "a" + "b" + "c"; 就等價於String s = "abc";
String a = "a";
String b = "b";
String c = "c";
String s = a + b + c;
這個就不同了,最終結果等於:
Stringbuffer temp = new Stringbuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();
(6) Integer、Double等包裝類和String有着一樣的特性:不變類。
String str = "abc"的內部工做機制頗有表明性,以Boolean爲例,說明一樣的問題。
不變類的屬性通常定義爲final,一旦構造完畢就不能再改變了。
Boolean對象只有有限的兩種狀態:true和false,將這兩個Boolean對象定義爲命名常量:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
這兩個命名常量和字符串常量同樣,在常數池中分配空間。 Boolean.TRUE是一個引用,Boolean.FALSE是一個引用,而"abc"也是一個引用!因爲Boolean.TRUE是類變量 (static)將靜態地分配內存,因此須要不少Boolean對象時,並不須要用new表達式建立各個實例,徹底能夠共享這兩個靜態變量。其JDK中源 代碼是:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
基本數據(Primitive)類型的自動裝箱(autoboxing)、拆箱(unboxing)是JSE 5.0提供的新功能。 Boolean b1 = 5>3; 等價於Boolean b1 = Boolean.valueOf(5>3); //優於Boolean b1 = new Boolean (5>3);
static void foo(){
boolean isTrue = 5>3; //基本類型
Boolean b1 = Boolean.TRUE; //靜態變量建立的對象
Boolean b2 = Boolean.valueOf(isTrue);//靜態工廠
Boolean b3 = 5>3;//自動裝箱(autoboxing)
System.out.println("b1 == b2 ?" +(b1 == b2));
System.out.println("b1 == b3 ?" +(b1 == b3));
Boolean b4 = new Boolean(isTrue);////不宜使用
System.out.println("b1 == b4 ?" +(b1 == b4));//浪費內存、有建立實例的時間開銷
} //這裏b一、b二、b3指向同一個Boolean對象。
(7) 若是問你:String x ="abc";建立了幾個對象?
準確的答案是:0或者1個。若是存在"abc",則變量x持有"abc"這個引用,而不建立任何對象。
若是問你:String str1 = new String("abc"); 建立了幾個對象?
準確的答案是:1或者2個。(至少1個在heap中)
(8) 對於int a = 3; int b = 3;
編譯器先處理int a = 3;首先它會在棧中建立一個變量爲a的引用,而後查找有沒有字面值爲3的地址,沒找到,就開闢一個存放3這個字面值的地址,而後將a指向3的地址。接着處 理int b = 3;在建立完b的引用變量後,因爲在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的狀況。
五、堆(Heap)和非堆(Non-heap)內存
按照官方的說法:「Java 虛擬機具備一個堆,堆是運行時數據區域,全部類實例和數組的內存均今後處分配。堆是在 Java 虛擬機啓動時建立的。」
能夠看出JVM主要管理兩種類型的內存:堆和非堆。
簡單來講堆就是Java代碼可及的內存,是留給開發人員使用的;
非堆就是JVM留給本身用的,因此方法區、JVM內部處理或優化所需的內存(如JIT編譯後的代碼緩存)、每一個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法的代碼都在非堆內存中。
堆內存分配
JVM初始分配的內存由-Xms指定,默認是物理內存的1/64;
JVM最大分配的內存由-Xmx指定,默認是物理內存的1/4。
默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制;空餘堆內存大於70%時,JVM會減小堆直到-Xms的最小限制。
所以服務器通常設置-Xms、-Xmx相等以免在每次GC 後調整堆的大小。
非堆內存分配
JVM使用-XX:PermSize設置非堆內存初始值,默認是物理內存的1/64;
由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4。
例子
-Xms256m
-Xmx1024m
-XX:PermSize=128M
-XX:MaxPermSize=256M數組