源碼|jdk源碼之Object及裝箱類型分析

jdk源碼讀到如今這裏,重要的集合類也讀了一部分了。
集合類再往下讀的話,就要涉及到兩個方向。
第一,是比較典型的可是不經常使用的數據結構,這部分我準備將數據結構複習、回顧後再繼續閱讀。
第二,是併發相關的集合,這部分我準備留到和併發相關的類一塊兒閱讀。java

因此,今天就讀些輕鬆的。緩存

<!--more -->數據結構

Object

做爲單根繼承的Object

java的對象系統設計是採用單根繼承,全部的對象往上追溯,Object都是它們共同的祖先。併發

有了這個假設,我突然想起java中一個有趣的事實:jvm

List<Integer> list = new ArrayList<Integer>();
list.toString();

這段代碼能正常編譯、運行嗎?經驗告訴我,固然能夠。
但是從類型系統的角度仔細思考,list引用的類型爲List<Integer>,其爲List接口。
然而,List接口中並無toString方法,爲何能調用?函數

這是因爲,在java中,會讓接口類型也擁有Object的全部方法。一個接口對象,也是一個Object對象。由於單根繼承這一整體設計,因此這樣設計接口是合理的。
這裏有關於該問題的有趣討論,因此這裏就不詳細展開了。工具

做爲鎖的Object

在java中,除了最基本的單根繼承的祖先類以外,Object還內置了不少機制。如:性能

Object o = new Object();
synchronized(o) {
    /* ... */
}

在其它語言中,鎖這一機制都是標準庫中提供的函數,成對使用。一個lock函數用於獲取鎖,一個release函數函數用於釋放鎖。優化

然而,java直接將鎖機制做爲語法的一部分,還給它一個專屬關鍵字synchronized。每一個Object對象,都內嵌了一個鎖。java稱之監視器鎖。this

這樣設計有什麼好處呢?一種觀點是,將鎖機制內置爲語法的一部分,有利於jvm對其進行深度優化提高性能,如java的鎖升級機制。

做爲條件變量的Object

java的Object不只能夠認爲內嵌了一把鎖,還內嵌了一個條件變量。操做條件變量的函數:

public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;

wait將當前線程在條件變量上阻塞,通常是爲了等待其餘線程的某件事情執行完成。當其餘線程的事情執行完成後,在條件變量上調用notifynotifyAll來喚醒阻塞的線程。

能夠看到,這三個方法都是native,jvm原生實現。

wait還有兩個重載形式:

public final void wait() throws InterruptedException {
    wait(0);
}

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}

比較有意思的是第二個。
原生實現的wait(long timeout),只能設置毫秒級別的超時時間。可是這個wait(long timeout, int nanos)卻能設置納秒級別的超時時間。怎麼實現的?

if (nanos > 0) {
    timeout++;
}

笑哭了。。。。難道是我下載的jdk平臺不對?

hashCode、equals、toString

Object類提供了這三個函數的默認實現。來看一下:

public native int hashCode();

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

public boolean equals(Object obj) {
    return (this == obj);
}

能夠看到,hashCode的默認方法是原生實現,究竟是不是指針不清楚。

equals方法的默認實現僅僅簡單比較了是否爲同一引用。

toString()方法打印出的是類名及十六進制的hash值。

裝箱拆箱

裝箱拆箱機制的存在的緣由是:

  1. java中的泛型是類型擦除,相似集合等泛型類中實際存放的必須是Object的子類,也即引用類型。
  2. java的8種基本類型都是值類型,不是對象。所以沒法直接放入泛型類對象中。

爲了解決這個衝突,只好設計一組對象,中間包裹基本類型,而且語法層次內建裝箱類與基本類型的自動轉換機制,也即自動裝箱拆箱。

下面以Integer爲例分析裝箱拆箱類的源碼。

Integer

大體看一下Integer中的組成。能夠發現有三個不一樣的部分:

  1. Integer類自己做爲裝箱容器。
  2. Integer類的static屬性定義了大量和int有關的常量。
  3. Integer類的static方法定義了和int有關的工具函數。

屬性和構造函數

先來看屬性。

private final int value;

public Integer(int value) {
    this.value = value;
}

對的,Integer對象中,只有包含這麼一個數據,被裝箱的原始值。
簡單到不能再簡單。

工廠方法和緩存

咱們知道,通常來講,在java中,使用工廠方法代替構造函數是更好的設計。在Integer裏,就體現了它的好處之一。

Integer提供了一組靜態工廠方法:

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

public static Integer valueOf(String s, int radix) throws NumberFormatException {
    return Integer.valueOf(parseInt(s,radix));
}

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

前兩個工廠方法都利用最後一個工廠方法實現。最重要的是最後一個。

很是明顯,當被裝箱的原始類型iIntegerCache.lowIntegerCache.high之間時,則返回緩存的Integer對象。

來看IntegerCache:

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() {}
    }

可發現:

  1. 默認緩存的值是-128127
  2. 緩存的範圍能夠經過java.lang.Integer.IntegerCache.high來設置。這樣,若是在某些場景下Integer影響性能,能夠經過jvm手動修改該參數空間換時間。

總結一下,因爲Integer是對象,而對整數的操做是代碼裏很是頻繁的地方。裝箱機制會致使程序產生大量的Integer對象,這致使:

  1. 對象會佔據額外空間(如對象頭),形成內存浪費。
  2. 頻繁建立銷燬對象,給gc形成壓力。

所以,採用緩存機制,儘可能下降裝箱對性能的影響。

其它裝箱類

其它裝箱類的代碼這裏就不分析了。重點關注下各裝箱類的緩存範圍。
首先,Boolean,只有兩個值,固然能夠都緩存。

浮點類型,Double和Float,沒有緩存:

public static Float valueOf(float f) {
    return new Float(f);
}

public static Double valueOf(double d) {
    return new Double(d);
}

Short,緩存範圍爲-128到127,和默認的Integer同樣。最重要的是,這個範圍沒法修改。

public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

    private static class ShortCache {
        private ShortCache(){}

        static final Short cache[] = new Short[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Short((short)(i - 128));
        }
    }

一樣:

  1. Byte緩存範圍也是-128到127,所有緩存。
  2. 而Character緩存範圍爲0到127.
  3. Long的緩存範圍爲-128到127。

能夠發現,只有Integer的緩存範圍可以修改,其它的裝箱類型都不行。

相關文章
相關標籤/搜索