1、背景
今天有一個朋友問到一個為什麼 ArrayList 源碼擴容方法中,數組長度最大值是 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 的問題(真的是MAX_ARRAY_SIZE? )。

并給出下列截圖:
2、别急,讓我們捋一捋
我們先搞清楚這裡幾個關鍵變量的含義:
- min capacity 這次擴容最小需要的容量
- old capacity 擴容前原始數組容量
- newCapacity = oldCapacity + (oldCapacity >> 1) 是預計要擴容到的容量
之前講過,看源碼看不太懂時要多看注釋。
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
這裡說 Some VMs reserve some header words in an array. 即有些虛拟機會在數組中儲存 header words 頭部字。
對象頭可以看這裡:
https://cloud.tencent.com/developer/article/1413543 https://stackoverflow.com/questions/26357186/what-is-in-java-object-headerPS: 數組有點特殊性,數組對象要額外存儲數組元素長度在頭部,少了這8個長度可能與此有關。
嘗試配置設定大于 MAX_ARRAY_SIZE 長度的數組會導緻 OOM (換句話說,超過了該虛拟機的數組長度限制)。
grow 函數的目的是:提高容量以便至少滿足最少 minCapacity 容量!!
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
有些虛拟機大于 MAX_ARRAY_SIZE (Integer.MAX -8 )就容易OOM (注意隻是有些)
注意前提是 new - MAX_ARRAY_SIZE >0 就意味着 正常情況下新的擴容長度大于了 MAX_ARRAY_SIZE。
此時最大可以擴容到 Integer.MAX,因為數組長度是整數。
因為數組理論上長度就是 Integer.MAX_VALUE 個别JVM 設計上的問題 咱們可以盡量照顧下 但并不是說一定因為個别JVM 就一定不讓擴容到 整數最大值長度。
如果再滿了 那麼對不起 直接到将數組長度設定為整數最大值, 愛咋咋地!
是以,數組最大容量是 Integer.MAX_VALUE (提問的說法有問題) ,在圖示情況擴容到 MAX_ARRAY_SIZE 是為了擴容到 MAX_ARRAY_SIZE以上長度就OOM的虛拟機可以盡量不OOM,如果還放不下沒辦法,對不起了大兄弟!
3 Learn more
你以為這就完了嗎?
其實上面的問題并不是重點。
看這這段代碼最重點在:
int newCapacity = oldCapacity + (oldCapacity >> 1);
為什麼不是擴容到 minCapacity +1?
其實是預先申請這麼多的容量,避免頻繁擴容,采用了空間換時間思想。
Redis 的動态字元串和這個有點類似,隻不過擴容政策有差異 。
那麼為什麼Redis 動态字元串擴容會有兩個處理方式?為什麼大于1M 每次增加1M 而且有最大長度限制呢?
1 首先想下 Redis 和 Java中的ArrayList的使用場景。
Redis 通常用作緩存,而且失效時間相對較長(少則幾秒鐘,多則幾分鐘,幾個小時等)。而ArrayList 通常在某個函數中用,一般來說生命周期很短,出棧後就可以回收。
2 Redis 為啥要有最大值限制。可能是因為通常和使用者不在同一個伺服器上,需要通過網絡進行傳輸,如果很大,傳輸很容易逾時,而且Redis 主任務為單線程,很容易阻塞其他任務的執行。
3 小于1M
4、總結
之前一直提倡看源碼一定要重視看注釋,當大家 JDK 或者其他開源項目源碼有困惑時一定要重視看源碼。
同樣地,當我們寫代碼時,有些情況容易讓别人困惑時,一定要加上注釋。
此外,讀源碼一定要像這位同學一樣,多一些思考。
讀源碼要讀源碼的設計理念,展現出來的設計思想。
讀源碼時建議先猜想後驗證,即猜想可能的原因,然後再去分析,這樣學到更多。
關于如何高效閱讀源碼可以看我另外一篇文章。
https://blog.csdn.net/w605283073/article/details/89290798對了,我最近在 GitChat上寫了三篇不錯的文章,大家感興趣可以讀讀:
《性能優化方法論》 《CodeReview 的正确姿勢》 《排錯避坑寶典》