摘要:對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。并不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息并不一定要經過對象本身,這點將在節討論。
目錄介紹
1.關于int和Integer的問題區別分析
2.Integer的值緩存的原理
2.1 Java 5 中引入緩存特性
2.2 Integer類中的IntegerCache類
2.3 其他整型類型的緩存機制
3.理解自動裝箱和拆箱
3.1 什么是裝箱?什么是拆箱?
3.2 裝箱和拆箱是如何實現的
3.3 裝箱和拆箱在編程實際中注意點
4.原始類型線程安全問題
4.1 那些類型是線程安全的
4.2 如何驗證int類型是否線程安全
4.3 AtomicInteger線程安全版
5.Java 原始數據類型和引用類型局限性
5.1 原始數據類型和 Java 泛型并不能配合使用
5.2 無法高效地表達數據,也不便于表達復雜的數據結構
6.關于其他知識延伸
6.1 對象的內存結構
6.2 對象頭的結構
6.3 如何計算或者獲取某個Java對象的大小
7.關于其他內容介紹
7.1 關于博客匯總鏈接
7.2 關于我的博客
1.關于int和Integer的問題區別分析1.1 編譯階段、運行時,自動裝箱 / 自動拆箱是發生在什么階段?
1.2使用靜態工廠方法 valueOf 會使用到緩存機制,那么自動裝箱的時候,緩存機制起作用嗎?
1.3為什么我們需要原始數據類型,Java 的對象似乎也很高效,應用中具體會產生哪些差異?
1.4 閱讀過 Integer 源碼嗎?分析下類或某些方法的設計要點?
1.5 int和Integer的區別
1、Integer是int的包裝類,int則是java的一種基本數據類型 2、Integer變量必須實例化后才能使用,而int變量不需要 3、Integer實際是對象的引用,當new一個Integer時,實際上是生成一個指針指向此對象;而int則是直接存儲數據值 4、Integer的默認值是null,int的默認值是0 延伸: 關于Integer和int的比較 1、由于Integer變量實際上是對一個Integer對象的引用,所以兩個通過new生成的Integer變量永遠是不相等的(因為new生成的是兩個對象,其內存地址不同)。 Integer i = new Integer(100); Integer j = new Integer(100); System.out.print(i == j); //false 2、Integer變量和int變量比較時,只要兩個變量的值是向等的,則結果為true(因為包裝類Integer和基本數據類型int比較時,java會自動拆包裝為int,然后進行比較,實際上就變為兩個int變量的比較) Integer i = new Integer(100); int j = 100; System.out.print(i == j); //true 3、非new生成的Integer變量和new Integer()生成的變量比較時,結果為false。(因為非new生成的Integer變量指向的是java常量池中的對象,而new Integer()生成的變量指向堆中新建的對象,兩者在內存中的地址不同) Integer i = new Integer(100); Integer j = 100; System.out.print(i == j); //false 4、對于兩個非new生成的Integer對象,進行比較時,如果兩個變量的值在區間-128到127之間,則比較結果為true,如果兩個變量的值不在此區間,則比較結果為false Integer i = 100; Integer j = 100; System.out.print(i == j); //true Integer i = 128; Integer j = 128; System.out.print(i == j); //false 對于第4條的原因: java在編譯Integer i = 100 ;時,會翻譯成為Integer i = Integer.valueOf(100);,而java API中對Integer類型的valueOf的定義如下: public static Integer valueOf(int i){ assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high){ return IntegerCache.cache[i + (-IntegerCache.low)]; } return new Integer(i); } java對于-128到127之間的數,會進行緩存,Integer i = 127時,會將127進行緩存,下次再寫Integer j = 127時,就會直接從緩存中取,就不會new了2.Integer的值緩存的原理 2.1 Java 5 中引入緩存特性
在 Java 5 中,為 Integer 的操作引入了一個新的特性,用來節省內存和提高性能。整型對象在內部實現中通過使用相同的對象引用實現了緩存和重用。
這種 Integer 緩存策略僅在自動裝箱(autoboxing)的時候有用,使用構造器創建的 Integer 對象不能被緩存。
2.2 Integer類中的IntegerCache類在創建新的 Integer 對象之前會先在 IntegerCache.cache (是個Integer類型的數組)中查找。有一個專門的 Java 類來負責 Integer 的緩存。
這個類是用來實現緩存支持,并支持 -128 到 127 之間的自動裝箱過程。最大值 127 可以通過 JVM 的啟動參數 -XX:AutoBoxCacheMax=size 修改。 緩存通過一個 for 循環實現。從小到大的創建盡可能多的整數并存儲在一個名為 cache 的整數數組中。這個緩存會在 Integer 類第一次被使用的時候被初始化出來。以后,就可以使用緩存中包含的實例對象,而不是創建一個新的實例(在自動裝箱的情況下)。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; 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) { try { 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); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }2.3 其他整型類型的緩存機制
這種緩存行為不僅適用于Integer對象。我們針對所有整數類型的類都有類似的緩存機制。
有 ByteCache 用于緩存 Byte 對象
有 ShortCache 用于緩存 Short 對象
有 LongCache 用于緩存 Long 對象
有 CharacterCache 用于緩存 Character 對象
Byte,Short,Long 有固定范圍: -128 到 127。對于 Character, 范圍是 0 到 127。除了 Integer 可以通過參數改變范圍外,其它的都不行。
3.理解自動裝箱和拆箱 3.1 什么是裝箱?什么是拆箱?裝箱就是 自動將基本數據類型轉換為包裝器類型;拆箱就是 自動將包裝器類型轉換為基本數據類型。
//拆箱 int yc = 5; //裝箱 Integer yc = 5;3.2 裝箱和拆箱是如何實現的
以Interger類為例,下面看一段代碼來了解裝箱和拆箱的實現
public class Main { public static void main(String[] args) { Integer y = 10; int c = i; } }
然后來編譯一下,看下圖所示:
從反編譯得到的字節碼內容可以看出,在裝箱的時候自動調用的是Integer的valueOf(int)方法。而在拆箱的時候自動調用的是Integer的intValue方法。
因此可以用一句話總結裝箱和拆箱的實現過程:裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的 xxxValue方法實現的。(xxx代表對應的基本數據類型)。
3.3 裝箱和拆箱在編程實際中注意點建議避免無意中的裝箱、拆箱行為,尤其是在性能敏感的場合,創建 10 萬個 Java 對象和 10 萬個整數的開銷可不是一個數量級的,不管是內存使用還是處理速度,光是對象頭的空間占用就已經是數量級的差距了。
4.原始類型線程安全問題 4.1 那些類型是線程安全的Java自帶的線程安全的基本類型包括: AtomicInteger, AtomicLong, AtomicBoolean, AtomicIntegerArray,AtomicLongArray等
4.2 如何驗證int類型是否線程安全200個線程,每個線程對共享變量 count 進行 50 次 ++ 操作
int 作為基本類型,直接存儲在內存棧,且對其進行+,-操作以及++,–操作都不是原子操作,都有可能被其他線程搶斷,所以不是線程安全。int 用于單線程變量存取,開銷小,速度快
int count = 0; private void startThread() { for (int i = 0;i < 200; i++){ new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 50; k++){ count++; } } }).start(); } // 休眠10秒,以確保線程都已啟動 try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); }finally { Log.e("打印日志----",count+""); } } //期望輸出10000,最后輸出的是9818 //注意:打印日志----: 98184.3 AtomicInteger線程安全版
AtomicInteger類中有有一個變量valueOffset,用來描述AtomicInteger類中value的內存位置 。
當需要變量的值改變的時候,先通過get()得到valueOffset位置的值,也即當前value的值.給該值進行增加,并賦給next
compareAndSet()比較之前取到的value的值當前有沒有改變,若沒有改變的話,就將next的值賦給value,倘若和之前的值相比的話發生變化的話,則重新一次循環,直到存取成功,通過這樣的方式能夠保證該變量是線程安全的
value使用了volatile關鍵字,使得多個線程可以共享變量,使用volatile將使得VM優化失去作用,在線程數特別大時,效率會較低。
private static AtomicInteger atomicInteger = new AtomicInteger(1); static Integer count1 = Integer.valueOf(0); private void startThread1() { for (int i = 0;i < 200; i++){ new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 50; k++){ // getAndIncrement: 先獲得值,再自增1,返回值為自增前的值 count1 = atomicInteger.getAndIncrement(); } } }).start(); } // 休眠10秒,以確保線程都已啟動 try { Thread.sleep(1000*10); } catch (InterruptedException e) { e.printStackTrace(); }finally { Log.e("打印日志----",count1+""); } } //期望輸出10000,最后輸出的是10000 //注意:打印日志----: 10000 //AtomicInteger使用了volatile關鍵字進行修飾,使得該類可以滿足線程安全。 private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; }5.Java 原始數據類型和引用類型局限性 5.1 原始數據類型和 Java 泛型并不能配合使用
Java 的泛型某種程度上可以算作偽泛型,它完全是一種編譯期的技巧,Java 編譯期會自動將類型轉換為對應的特定類型,這就決定了使用泛型,必須保證相應類型可以轉換為Object。
5.2 無法高效地表達數據,也不便于表達復雜的數據結構Java 的對象都是引用類型,如果是一個原始數據類型數組,它在內存里是一段連續的內存,而對象數組則不然,數據存儲的是引用,對象往往是分散地存儲在堆的不同位置。這種設計雖然帶來了極大靈活性,但是也導致了數據操作的低效,尤其是無法充分利用現代 CPU 緩存機制。
Java 為對象內建了各種多態、線程安全等方面的支持,但這不是所有場合的需求,尤其是數據處理重要性日益提高,更加高密度的值類型是非常現實的需求。
6.關于其他知識延伸 6.1 對象的內存結構對象在內存中存儲的布局可以分為3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
6.2 對象頭的結構HotSpot虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等,這部分數據的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit,官方稱它為"Mark Word"。
對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。并不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息并不一定要經過對象本身,這點將在2.3.3節討論。另外,如果對象是一個Java數組,那在對象頭中還必須有一塊用于記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中卻無法確定數組的大小。
6.3 如何計算或者獲取某個Java對象的大小獲取一個JAVA對象的大小,可以將一個對象進行序列化為二進制的Byte,便可以查看大小
//獲取一個JAVA對象的大小,可以將一個對象進行序列化為二進制的Byte,便可以查看大小 Integer value = 10; ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos ; try { oos = new ObjectOutputStream(bos); oos.writeObject(value); oos.close(); } catch (IOException e) { e.printStackTrace(); } // 讀出當前對象的二進制流信息 Log.e("打印日志----",bos.size()+""); //打印日志----: 817.關于其他內容介紹 7.1 關于博客匯總鏈接
1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
7.2 關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97088.html
摘要:但是,如果像上例中只取最后幾位的時候,這可不是什么好事,即使我的數據分布很散亂,但是哈希沖突仍然會很嚴重。由于我們所創建的是類型的,這也是最巧的一點,類型的返回值就是其值本身,而存儲的時候元素通過一些運算后會得出自己在數組中所處的位置。 HashSet 是否無序 (一) 問題起因: 《Core Java Volume I—Fundamentals》中對HashSet的描述是這樣的: H...
摘要:本章部分內容從源碼中解讀一些自動裝箱與拆箱的原理,以及會出現的一些陷阱已經性能等。例題分析我們通過幾個經典的問題,來看看大家到底理解了裝箱與拆箱的知識點沒。 showImg(https://img-blog.csdnimg.cn/20190426221838971.gif);showImg(https://img-blog.csdnimg.cn/20190426221918208.pn...
摘要:接下來看下如果使用提供的接口會有哪些改進首先看下接口定義省略該函數式接口唯一的抽象方法接收一個參數,有返回值。是不是有點體驗到函數式編程的靈活之處。 上一篇文章中,我們總體介紹了創建函數式接口實例的幾種方式以及Java8中接口新增的默認方法特性,接下來我們來看下Java8中已經為我們提供的幾種典型的函數式接口先看一個示例 public class FunctionTest { ...
摘要:相等判斷符介紹相等判斷符用于比較基本數據類型和引用類型數據當比較基本數據類型的時候比較的是數值當比較引用類型數據時比較的是引用指針判斷基本類型是否相等首先基本數據類型指的是中的八大數據類型這八大基本數據類型有個共同的特點是它們在內存中是有具相等判斷符==介紹 ? ==相等判斷符用于比較基本數據類型和引用類型數據. 當比較基本數據類型的時候比較的是數值, 當比較引用類型數據時比較的是引用(指...
摘要:相等判斷符介紹相等判斷符用于比較基本數據類型和引用類型數據當比較基本數據類型的時候比較的是數值當比較引用類型數據時比較的是引用指針判斷基本類型是否相等首先基本數據類型指的是中的八大數據類型這八大基本數據類型有個共同的特點是它們在內存中是有具相等判斷符==介紹 ==相等判斷符用于比較基本數據類型和引用類型數據. 當比較基本數據類型的時候比較的是數值, 當比較引用類型數據時比較的是引用(指針...
閱讀 1538·2021-11-04 16:10
閱讀 2774·2021-09-30 09:48
閱讀 2839·2019-08-29 11:31
閱讀 1578·2019-08-28 18:22
閱讀 3225·2019-08-26 13:44
閱讀 1319·2019-08-26 13:42
閱讀 2845·2019-08-26 10:20
閱讀 754·2019-08-23 17:00