一 Java內存分配
一般Java在內存分配時會涉及到以下區(qū)域:
- 寄存器:我們在程序中無法控制
- 棧:存放 基本數據類型的數據和 對象的引用 赡鲜,但對象本身不存放在棧中睬塌,而是存放在堆中
- 堆:存放new產生的數據
- 靜態(tài)域:存放在對象中用static定義的靜態(tài)成員
- 常量池: 存放常量
- 非RAM存儲:硬盤永久存儲空間
堆
堆內存用來存放由new創(chuàng)建的對象和數組。在堆中分配的內存昆庇,有Java虛擬機的垃圾回收器管理末贾。
在堆中產生了一個數組或對象后,還可以 在棧中定義一個特殊的變量凰锡,讓棧中這個變量的取值等于數組或對象在堆內存中的首地址未舟,棧中的這個變量就成了數組或對象的引用變量。
引用變量是普通的變量掂为,定義時在棧中分配裕膀,引用變量在程序運行到其作用域之外后被釋放。而數組和對象本身在堆中分配勇哗,即使程序 運行到使用 new 產生數組或者對象的語句所在的代碼塊之外昼扛,數組和對象本身占據的內存不會被釋放,數組和對象在沒有引用變量指向它的時候欲诺,才變?yōu)槔常荒茉诒皇褂茫?然占據內存空間不放扰法,在隨后的一個不確定的時間被垃圾回收器收走(釋放掉)蛹含。
棧
函數中定義的一些基本類型的數據變量和對象的引用變量都在函數的棧內存中分配。
棧的優(yōu)勢是存取速度比堆要快塞颁,僅次于直接位于CPU 的寄存器浦箱,而且數據可以共享。但是存在棧中的數據大小與生存周期必須是確定的祠锣。
當在一段代碼塊定義一個變量時酷窥,Java就在棧中 為這個變量分配內存空間,當該變量退出該作用域后伴网,Java會自動釋放掉為該變量所分配的內存空間蓬推,該內存空間可以立即被另作他用。
二 基本數據類型
-
基本數據類型(原始數據類型): byte short int long float double char boolean
基本類型的變量持有原始值澡腾。這種類型是通過諸如 int a=7; 的形式來定義的沸伏,稱為自動變量。這里自動變量是字面值动分。不是類的實例毅糟,即不是類的引用,這里并沒有類的存在刺啦。a 是指向一個 int 類型的引用,指向 7 這個字面值纠脾。由于其大小確定生存期可知(這些定義在某個程序塊中玛瘸,程序塊退出后蜕青,字段值就消失),因此存在棧中.
由于棧的數據可以共享糊渊,因此int a=3; int b=3;
這段代碼右核,編譯器首先處理int a =3;
,先會在棧中創(chuàng)建一個變量為 a 的引用,然后查找有沒有字面值為 3的地址渺绒,沒有找到贺喝,就開辟一個存放 3 這個字面值的地址,然后將a 指向 3 的地址宗兼。接下來處理int b =3;
在創(chuàng)建完 b 這個引用變量后躏鱼,由于棧中已經有 3 這個字面值,便將 b 指向 3 的地址殷绍∪究粒【定義變量,給變量賦值】實際上主到,Java中還存在另外一種基本類型
void
茶行,它也有對應的包裝類java.lang.Void
,不過我們無法直接對它們進行操作登钥。
三 包裝類數據
1. 為什么需要包裝類
Java中的基本類型不是面向對象的畔师,它們只是純粹的數據,除了數值本身的信息之外牧牢,基本類型數據不帶有其他信息或者可操作方法看锉。這在實際使用中存在很多不足,比如结执,在集合類中度陆,我們是無法將int 、double等類型放進去的献幔。因為集合的容器要求元素是Object類型懂傀。為了解決這個不足,對每個基本類型都對應了一個引用的類型蜡感,稱為裝箱基本類型蹬蚁。
為了讓基本類型也具有對象的特征,就出現了包裝類型郑兴,它相當于將基本類型“包裝起來”犀斋,使得它具有了對象的性質,并且為其添加了屬性和方法情连,豐富了基本類型的操作叽粹。
2. 拆箱與裝箱
把基本數據類型轉換成包裝類的過程就是打包裝,英文對應于boxing,中文翻譯為裝箱虫几。
反之锤灿,把包裝類轉換成基本數據類型的過程就是拆包裝,英文對應于unboxing辆脸,中文翻譯為拆箱但校。
在Java SE5之前,可以通過以下代碼進行裝箱:
Integer i = new Integer(6);
3. 自動拆箱與自動裝箱
自動裝箱: 將基本數據類型自動轉換成對應的包裝類啡氢。
自動拆箱:將包裝類自動轉換成對應的基本數據類型状囱。
Integer i =6; //自動裝箱
int j= i; //自動拆箱
4. 自動裝箱與自動拆箱的實現原理
自動拆裝箱的代碼:
public static void main(String[]args){
Integer integer=1; //裝箱
int i=integer; //拆箱
}
反編譯以上代碼后可以得到以下代碼:
public static void main(String[]args){
Integer integer=Integer.valueOf(1);
int i=integer.intValue();
}
從反編譯后的代碼可以看出,int的自動裝箱都是通過Integer.valueOf()
方法來實現的倘是,Integer的自動拆箱都是通過integer.intValue
來實現的亭枷。如果讀者感興趣,可以試著將八種類型都反編譯一遍 辨绊,你會發(fā)現以下規(guī)律:
自動裝箱都是通過包裝類的
valueOf()
方法來實現的奶栖。自動拆箱都是通過包裝類對象的xxxValue()
來實現的。
5. 整型的取值范圍
Java中的整型主要包含byte
门坷、short
宣鄙、int
和long
這四種,表示的數字范圍也是從小到大的默蚌,之所以表示范圍不同主要和他們存儲數據時所占的字節(jié)數有關冻晤。
整型的這幾個類型中,
- byte:byte用1個字節(jié)來存儲绸吸,范圍為-128(-27)到127(27-1)鼻弧,在變量初始化的時候,byte類型的默認值為0锦茁。
- short:short用2個字節(jié)存儲攘轩,范圍為-32,768 (-2^15)到32,767 (2^15-1),在變量初始化的時候码俩,short類型的默認值為0度帮,一般情況下,因為Java本身轉型的原因稿存,可以直接寫為0笨篷。
- int:int用4個字節(jié)存儲,范圍為-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1)瓣履,在變量初始化的時候率翅,int類型的默認值為0。
- long:long用8個字節(jié)存儲袖迎,范圍為-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1)冕臭,在變量初始化的時候腺晾,long類型的默認值為0L或0l,也可直接寫為0辜贵。
6. 超出范圍怎么辦
上面說過了丘喻,整型中,每個類型都有一定的表示范圍念颈,但是,在程序中有些計算會導致超出表示范圍连霉,即溢出榴芳。如以下代碼:
int i = Integer.MAX_VALUE;
int j = Integer.MAX_VALUE;
int k = i + j;
System.out.println(k); //輸出: -2
這就是發(fā)生了溢出,溢出的時候并不會拋異常跺撼,也沒有任何提示窟感。所以,在程序中歉井,使用同類型的數據進行運算的時候柿祈,一定要注意數據溢出的問題。
7. 緩存
Java SE的自動拆裝箱提供了一個和緩存有關的功能哩至,看如下代碼:
public static void main(String... strings) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
}
我們普遍認為上面的兩個判斷的結果都是false躏嚎。雖然比較的值是相等的,但是由于比較的是對象菩貌,而對象的引用不一樣卢佣,所以會認為兩個if判斷都是false的。在Java中箭阶,==比較的是對象引用虚茶,而equals比較的是值。所以仇参,在這個例子中嘹叫,不同的對象有不同的引用,所以在進行比較的時候都將返回false诈乒。奇怪的是罩扇,這里兩個類似的if條件判斷返回不同的布爾值。
上面這段代碼真正的輸出結果:
integer1 == integer2
integer3 != integer4
原因就和Integer中的緩存機制有關抓谴。在Java 5中暮蹂,在Integer的操作上引入了一個新功能來節(jié)省內存和提高性能。整型對象通過使用相同的對象引用實現了緩存和重用癌压。
適用于整數值區(qū)間-128 至 +127仰泻。
只適用于自動裝箱。使用構造函數創(chuàng)建對象不適用滩届。
緩存其實是由valueOf
方法實現的集侯,該方法有可能通過緩存經常請求的值而顯著提高空間和時間性能 被啼。
查看Integer的valueOf
方法:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
//static final int low = -128;
//當-128=<i<=127的時候,就直接在緩存中取出 i de Integer 類型對象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//否則就在堆內存中創(chuàng)建
return new Integer(i);
}
看出對于范圍 [-128,127] 的整數棠枉,valueOf
方法做了特殊處理浓体。采用IntegerCache.cache[i + (-IntegerCache.low)];
這個方法。
查看 IntegerCache
類的實現為:
private static class IntegerCache {
static final int low = -128; //最小值是固定的
static final int high;
static final Integer cache[];//cache 緩存是一個存放Integer類型的數組
static { //初始化辈讶,最大值可以配置
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1]; //初始化數組
int j = low;
//緩存區(qū)間數據
for(int k = 0; k < cache.length; k++)
//將-128~127包裝成256個對象存入緩存
cache[k] = new Integer(j++);
}
private IntegerCache() {}
IntegerCache
初始化后內存中就有Integer
緩沖區(qū)cache[]
了命浴,-128~127
區(qū)間的int
值有其對應的的包裝對象。這就是 valueOf
方法真正的優(yōu)化方法贱除。
public class ZhuangXaing {
public static void main(String[] args) {
Integer i= new Integer(12);
Integer j=12;
Integer k=Integer.valueOf(12);
Integer l= new Integer(232);
Integer m=232;
Integer n=232;
Double q = 232.0;
System.out.println("use ==.......");
System.out.println(i==12);
System.out.println(i==j);
System.out.println(j==k);
System.out.println(l==232);
System.out.println(l==m);
System.out.println(m==n);
System.out.println("use equals.....");
System.out.println(m.equals(n));
System.out.println(m.equals(q));
}
}
輸出結果:
use ==.......
true
false
true
true
false
false
use equals.....
true
false
Integer i= new Integer(12);
是指明了在堆內存中創(chuàng)建對象生闲;
Integer j=12;
是自動裝箱,調用valueOf 方法月幌,返回return IntegerCache.cache[12 + 128]
碍讯, 得到的是Integer 緩沖池中的對象。Integer k=Integer.valueOf(12);
與Integer j=12;
本質上相同扯躺,指向緩沖池中同一對象捉兴。包裝對象與數值比較,自動拆箱录语。
而對于大于127 的數值倍啥,執(zhí)行的都是return new Integer(i)
都在堆內存中,但是地址不同澎埠。
對于equals 方法比較的是數值大卸涸浴:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
可以看出比較的 obj
如果是Integer
的實例,則比較拆箱后數值的是否相等失暂。否則返回false彼宠。
下面這段代碼輸出結果是什么:
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
//false
//false
因為Double
類的valueOf
方法會采用與Integer
類的valueOf
方法不同的實現。很簡單:在某個范圍內的整型數值的個數是有限的弟塞,而浮點數卻不是凭峡。
其他的包裝器:
Boolean: (全部緩存)
Byte: (全部緩存)
Character ( <=127 緩存)
Short (-128~127 緩存)
Long (-128~127 緩存)
Float (沒有緩存)
Doulbe (沒有緩存)
下面這段代碼輸出結果是什么:
public class Main {
public static void main(String[] args) {
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
先看Boolean
類的源碼 ,valueOf
方法的實現:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
而其中的 TRUE
和FALSE
又是什么呢决记?在Boolean
中定義了2個靜態(tài)成員屬性:
public static final Boolean TRUE = new Boolean(true);
/**
* The <code>Boolean</code> object corresponding to the primitive
* value <code>false</code>.
*/
public static final Boolean FALSE = new Boolean(false);
由此可知上面代碼輸出都為true 摧冀。
8. 自動拆箱導致空指針異常
Map<String,Boolean> map = new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : false);
執(zhí)行該代碼,會報NullPointerException系宫。
Exception in thread "main" java.lang.NullPointerException
既然報了空指針索昂,那么一定是有些地方調用了一個null的對象的某些方法。在這短短的兩行代碼中扩借,看上去只有一處方法調用map.get("test")
椒惨,但是我們也都是知道,map已經事先初始化過了潮罪,不會是Null康谆,那么到底是哪里有空指針呢领斥。反編譯上面代碼:
HashMap hashmap = new HashMap();
Boolean boolean1 = Boolean.valueOf(hashmap == null ? false : ((Boolean)hashmap.get("test")).booleanValue());
可以看出((Boolean)hashmap.get("test")).booleanValue()
的執(zhí)行過程中報了空指針:
hashmap.get(“test”)->null;
(Boolean)null->null;
null.booleanValue()->報錯
在三目運算符的語法規(guī)范中,當第二沃暗,第三位操作數分別為基本類型和對象時月洛,其中的對象就會拆箱為基本類型進行操作。
由于使用了三目運算符孽锥,并且第二嚼黔、第三位操作數分別是基本類型和對象。所以對對象進行拆箱操作惜辑,由于該對象為null隔崎,所以在拆箱過程中調用null.booleanValue()的時候就報了NullPointerException. 。
正確寫法
Map<String,Boolean> map = new HashMap<String, Boolean>();
Boolean b = (map!=null ? map.get("test") : Boolean.FALSE);
保證三目運算符的第二第三位操作數都為對象類型即可韵丑。