字節(jié)跳動(dòng)面試官這樣問(wèn)有關(guān)字符串的問(wèn)題?。?/h1>
作者個(gè)人研發(fā)的在高并發(fā)場(chǎng)景下,提供的簡(jiǎn)單、穩(wěn)定、可擴(kuò)展的延遲消息隊(duì)列框架,具有精準(zhǔn)的定時(shí)任務(wù)和延遲隊(duì)列處理功能。自開(kāi)源半年多以來(lái),已成功為十幾家中小型企業(yè)提供了精準(zhǔn)定時(shí)調(diào)度方案,經(jīng)受住了生產(chǎn)環(huán)境的考驗(yàn)。為使更多童鞋受益,現(xiàn)給出開(kāi)源框架地址:https://github.com/sunshinelyz/mykit-delay
寫(xiě)在前面
對(duì)于Java中的String類(lèi)占用多大的內(nèi)存空間這個(gè)問(wèn)題,是最近面試中問(wèn)的比較多的一個(gè)問(wèn)題。很多小伙伴的回答的都不是很正確,有說(shuō)不占空間的,有說(shuō)1個(gè)字節(jié)的,有說(shuō)2個(gè)字節(jié)的,有說(shuō)3個(gè)字節(jié)的,有說(shuō)不知道的,更讓人哭笑不得的是竟然還有人說(shuō)是2的31次方。那如果真是這樣的話,服務(wù)器的內(nèi)存空間還放不下一個(gè)字符串呀!作為程序員的我們,可不能鬧這種笑話呀。今天,我們就一起來(lái)聊聊Java中的String到底占用多大的內(nèi)存空間!
Java對(duì)象的結(jié)構(gòu)
首先,我們來(lái)下Java對(duì)象在虛擬機(jī)中的結(jié)構(gòu),這里,以HotSpot虛擬機(jī)為例。
注:圖片來(lái)源http://r6d.cn/wp7q
從上面的這張圖里面可以看出,對(duì)象在內(nèi)存中的結(jié)構(gòu)主要包含以下幾個(gè)部分:
- Mark Word(標(biāo)記字段):對(duì)象的Mark Word部分占4個(gè)字節(jié),其內(nèi)容是一系列的標(biāo)記位,比如輕量級(jí)鎖的標(biāo)記位,偏向鎖標(biāo)記位等等。
- Klass Pointer(Class對(duì)象指針):Class對(duì)象指針的大小也是4個(gè)字節(jié),其指向的位置是對(duì)象對(duì)應(yīng)的Class對(duì)象(其對(duì)應(yīng)的元數(shù)據(jù)對(duì)象)的內(nèi)存地址
- 對(duì)象實(shí)際數(shù)據(jù):這里面包括了對(duì)象的所有成員變量,其大小由各個(gè)成員變量的大小決定,比如:byte和boolean是1個(gè)字節(jié),short和char是2個(gè)字節(jié),int和float是4個(gè)字節(jié),long和double是8個(gè)字節(jié),reference是4個(gè)字節(jié)
- 對(duì)齊:最后一部分是對(duì)齊填充的字節(jié),按8個(gè)字節(jié)填充。
換種說(shuō)法就是:
- 對(duì)象頭(object header):8 個(gè)字節(jié)(保存對(duì)象的 class 信息、ID、在虛擬機(jī)中的狀態(tài))
- Java 原始類(lèi)型數(shù)據(jù):如 int, float, char 等類(lèi)型的數(shù)據(jù)
- 引用(reference):4 個(gè)字節(jié)
- 填充符(padding)
Java中的String類(lèi)型
空String占用的空間
這里,我們以Java8為例進(jìn)行說(shuō)明。首先,我們來(lái)看看String類(lèi)中的成員變量。
- /** The value is used for character storage. */
- private final char value[];
- /** Cache the hash code for the string */
- private int hash; // Default to 0
- /** use serialVersionUID from JDK 1.0.2 for interoperability */
- private static final long serialVersionUID = -6849794470754667710L;
在 Java 里數(shù)組也是對(duì)象,因此數(shù)組也有對(duì)象頭。所以,一個(gè)數(shù)組所占的空間為對(duì)象頭所占的空間加上數(shù)組長(zhǎng)度加上數(shù)組的引用,即 8 + 4 + 4= 16 字節(jié) 。
所以,我們可以得出一個(gè)空String對(duì)象所占用的內(nèi)存空間,如下所示。
- 對(duì)象頭(8 字節(jié))+ 引用 (4 字節(jié) ) + char 數(shù)組(16 字節(jié))+ 1個(gè) int(4字節(jié))+ 1個(gè)long(8字節(jié))= 40 字節(jié)
所以,小伙伴們,你們的回答正確嗎?
非空String占用的空間
如果String字符串的長(zhǎng)度大于0的話,我們也可以得出String占用內(nèi)存的計(jì)算公式,如下所示。
- 40 + 2 * n
其中,n為字符串的長(zhǎng)度。
這里,可能有小伙伴會(huì)問(wèn),為什么是 40 + 2 * n 呢?這是因?yàn)?0是空字符串占用的內(nèi)存空間,這個(gè)我們上面已經(jīng)說(shuō)過(guò)了,String類(lèi)實(shí)際上是把數(shù)據(jù)存儲(chǔ)到char[]這個(gè)成員變量數(shù)組中的,而char[]數(shù)組中的一個(gè)char類(lèi)型的數(shù)據(jù)占用2個(gè)字節(jié)的空間,所以,只是String中的數(shù)據(jù)就會(huì)占用 2 * n(n為字符串的長(zhǎng)度)個(gè)字節(jié)的空間,再加上空字符串所占用的40個(gè)字節(jié)空間,最終得出一個(gè)字符串所占用的存儲(chǔ)空間為:40 + 2 * n (n為字符串長(zhǎng)度)。
因此在代碼中大量使用String對(duì)象時(shí),應(yīng)考慮內(nèi)存的實(shí)際占用情況。
注:40 + 2 * n 這個(gè)公式我們可以看成是計(jì)算String對(duì)象占用多大內(nèi)存空間的通用公式。
驗(yàn)證結(jié)論
接下來(lái),我們就一起來(lái)驗(yàn)證下我們上面的結(jié)論。首先,創(chuàng)建一個(gè)UUIDUtils類(lèi)用來(lái)生成32位的UUID,如下所示。
- package io.mykit.binghe.string.test;
- import java.util.UUID;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 生成沒(méi)有-的UUID
- */
- public class UUIDUtils {
- public static String getUUID(){
- String uuid = UUID.randomUUID().toString();
- return uuid.replace("-", "");
- }
- }
接下來(lái),創(chuàng)建一個(gè)TestString類(lèi),在main()方法中創(chuàng)建一個(gè)長(zhǎng)度為4000000的數(shù)組,然后在數(shù)組中放滿(mǎn)UUID字符串,如下所示。
- package io.mykit.binghe.string.test;
- import java.util.UUID;
- /**
- * @author binghe
- * @version 1.0.0
- * @description 測(cè)試String占用的內(nèi)存空間
- */
- public class TestString{
- public static void main(String[] args){
- String[] strContainer = new String[4000000];
- for(int i = 0; i < 4000000; i++){
- strContainer[i] = UUIDUtils.getUUID();
- System.out.println(i);
- }
- //防止程序退出
- while(true){
- }
- }
- }
這里,4000000個(gè)字符串,每個(gè)字符串的長(zhǎng)度為32,所以保存字符串?dāng)?shù)據(jù)所占用的內(nèi)存空間為:(40 + 32 * 2) * 4000000 = 416000000字節(jié),約等于416MB。
我們使用Jprofiler內(nèi)存分析工具進(jìn)行分析:
可以看到,使用Jprofiler內(nèi)存分析工具的結(jié)果為:321MB + 96632KB,約等于417MB。之所以使用Jprofiler內(nèi)存分析工具得出的結(jié)果比我們計(jì)算的大些,是因?yàn)樵诔绦驅(qū)嶋H運(yùn)行的過(guò)程中,程序內(nèi)部也會(huì)生成一些字符串,這些字符串也會(huì)占用內(nèi)存空間!!
所以,使用Jprofiler內(nèi)存分析工具得出的結(jié)果符合我們的預(yù)期。
本文轉(zhuǎn)載自微信公眾號(hào)「冰河技術(shù)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系冰河技術(shù)公眾號(hào)。