最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java性能優(yōu)化之關(guān)于大對象復(fù)用的目標(biāo)和注意點(diǎn)

 更新時(shí)間:2024年03月27日 10:37:00   作者:知識記錄者-vincent  
這篇文章主要介紹了Java性能優(yōu)化之關(guān)于大對象復(fù)用的目標(biāo)和注意點(diǎn),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

對于“大對象”的優(yōu)化。“大對象”是一個(gè)泛化概念,它可能存放在 JVM 中,也可能正在網(wǎng)絡(luò)上傳輸,也可能存在于數(shù)據(jù)庫中

為什么大對象會影響我們的應(yīng)用性能呢?

  • 第一,大對象占用的資源多,垃圾回收器要花一部分精力去對它進(jìn)行回收;
  • 第二,大對象在不同的設(shè)備之間交換,會耗費(fèi)網(wǎng)絡(luò)流量,以及昂貴的 I/O;
  • 第三,對大對象的解析和處理操作是耗時(shí)的,對象職責(zé)不聚焦,就會承擔(dān)額外的性能開銷

結(jié)合緩存,以及對象的池化操作,加上對一些中間結(jié)果的保存,我們能夠?qū)Υ髮ο筮M(jìn)行初步的提速,但這還遠(yuǎn)遠(yuǎn)不夠,我們僅僅減少了對象的創(chuàng)建頻率,但并沒有改變對象“大”這個(gè)事實(shí)

String 的 substring 方法

String 在 Java 中是不可變的,如果你改動(dòng)了其中的內(nèi)容,它就會生成一個(gè)新的字符串。

如果我們想要用到字符串中的一部分?jǐn)?shù)據(jù),就可以使用 substring 方法

如上圖所示,當(dāng)我們需要一個(gè)子字符串的時(shí)候,substring 生成了一個(gè)新的字符串,這個(gè)字符串通過構(gòu)造函數(shù)的 Arrays.copyOfRange 函數(shù)進(jìn)行構(gòu)造,這個(gè)函數(shù)在 JDK7 之后是沒有問題的,但在 JDK6 中,卻有著內(nèi)存泄漏的風(fēng)險(xiǎn)

上圖是從 JDK 官方的一張截圖。

可以看到,它在創(chuàng)建子字符串的時(shí)候,并不只拷貝所需要的對象,而是把整個(gè) value 引用了起來。

如果原字符串比較大,即使不再使用,內(nèi)存也不會釋放

比如,一篇文章內(nèi)容可能有幾兆,我們僅僅是需要其中的摘要信息,也不得不維持整個(gè)的大對象

String content = dao.getArticle(id); 
String summary=content.substring(0,100); 
articles.put(id,summary);

有一些工作年限比較長的面試官,對 substring 還停留在 JDK6 的印象,但其實(shí),Java 已經(jīng)將這個(gè) bug 給修改了

這對我們的借鑒意義是:如果你創(chuàng)建了比較大的對象,并基于這個(gè)對象生成了一些其他的信息,這個(gè)時(shí)候,一定要記得去掉和這個(gè)大對象的引用關(guān)系

集合大對象擴(kuò)容

對象擴(kuò)容,在 Java 中是司空見慣的現(xiàn)象,比如 StringBuilder、StringBuffer、HashMap,ArrayList 等。

概括來講,Java 的集合,包括 List、Set、Queue、Map 等,其中的數(shù)據(jù)都不可控。

在容量不足的時(shí)候,都會有擴(kuò)容操作,擴(kuò)容操作需要重新組織數(shù)據(jù),所以都不是線程安全的

StringBuilder 的擴(kuò)容代碼

void expandCapacity(int minimumCapacity) { 
        int newCapacity = value.length * 2 + 2; 
        if (newCapacity - minimumCapacity < 0) 
            newCapacity = minimumCapacity; 
        if (newCapacity < 0) { 
            if (minimumCapacity < 0) // overflow 
                throw new OutOfMemoryError(); 
            newCapacity = Integer.MAX_VALUE; 
        } 
        value = Arrays.copyOf(value, newCapacity); 
}

容量不夠的時(shí)候,會將內(nèi)存翻倍,并使用 Arrays.copyOf 復(fù)制源數(shù)據(jù)

HashMap 的擴(kuò)容代碼,擴(kuò)容后大小也是翻倍。它的擴(kuò)容動(dòng)作就復(fù)雜得多,除了有負(fù)載因子的影響,它還需要把原來的數(shù)據(jù)重新進(jìn)行散列,由于無法使用 native 的 Arrays.copy 方法,速度就會很慢

void addEntry(int hash, K key, V value, int bucketIndex) { 
        if ((size >= threshold) && (null != table[bucketIndex])) { 
            resize(2 * table.length); 
            hash = (null != key) ? hash(key) : 0; 
            bucketIndex = indexFor(hash, table.length); 
        } 
        createEntry(hash, key, value, bucketIndex); 
} 
 
void resize(int newCapacity) { 
        Entry[] oldTable = table; 
        int oldCapacity = oldTable.length; 
        if (oldCapacity == MAXIMUM_CAPACITY) { 
            threshold = Integer.MAX_VALUE; 
            return; 
        } 
        Entry[] newTable = new Entry[newCapacity]; 
        transfer(newTable, initHashSeedAsNeeded(newCapacity)); 
        table = newTable; 
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); 
}

List 的代碼也是阻塞性的,擴(kuò)容策略是原長度的 1.5 倍

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;
}

由于集合在代碼中使用的頻率非常高,如果你知道具體的數(shù)據(jù)項(xiàng)上限,那么不妨設(shè)置一個(gè)合理的初始化大小。

比如,HashMap 需要 1024 個(gè)元素,需要 7 次擴(kuò)容,會影響應(yīng)用的性能。

面試中會頻繁出現(xiàn)這個(gè)問題,你需要了解這些擴(kuò)容操作對性能的影響。

但是要注意,像 HashMap 這種有負(fù)載因子的集合(0.75),初始化大小 = 需要的個(gè)數(shù)/負(fù)載因子+1,如果你不是很清楚底層的結(jié)構(gòu),那就不妨保持默認(rèn)

保持合適的對象粒度

實(shí)際案例

有一個(gè)并發(fā)量非常高的業(yè)務(wù)系統(tǒng),需要頻繁使用到用戶的基本數(shù)據(jù)。如下圖所示,由于用戶的基本信息,都是存放在另外一個(gè)服務(wù)中,所以每次用到用戶的基本信息,都需要有一次網(wǎng)絡(luò)交互。

更加讓人無法接受的是,即使是只需要用戶的性別屬性,也需要把所有的用戶信息查詢,拉取一遍

為了加快數(shù)據(jù)的查詢速度,對數(shù)據(jù)進(jìn)行了初步的緩存,放入到了 Redis 中,查詢性能有了大的改善,但每次還是要查詢很多冗余數(shù)據(jù)

原始的 redis key 是這樣設(shè)計(jì)的

  • type: string 
  • key: user_${userid}  
  • value: json

這樣的設(shè)計(jì)有兩個(gè)問題

  • 查詢其中某個(gè)字段的值,需要把所有 json 數(shù)據(jù)查詢出來,并自行解析;
  • 更新其中某個(gè)字段的值,需要更新整個(gè) json 串,代價(jià)較高

針對這種大粒度 json 信息,就可以采用打散的方式進(jìn)行優(yōu)化,使得每次更新和查詢,都有聚焦的目標(biāo)

接下來對 Redis 中的數(shù)據(jù)進(jìn)行了以下設(shè)計(jì),采用 hash 結(jié)構(gòu)而不是 json 結(jié)構(gòu)

  • type: hash  
  • key: user_${userid} 
  • value: {sex:f, id:1223, age:23}

這樣,我們使用 hget 命令,或者 hmget 命令,就可以獲取到想要的數(shù)據(jù),加快信息流轉(zhuǎn)的速度

Bitmap 把對象變小

除了以上操作,還能再進(jìn)一步優(yōu)化嗎?比如,我們系統(tǒng)中就頻繁用到了用戶的性別數(shù)據(jù),用來發(fā)放一些禮品,推薦一些異性的好友,定時(shí)循環(huán)用戶做一些清理動(dòng)作等;或者,存放一些用戶的狀態(tài)信息,比如是否在線,是否簽到,最近是否發(fā)送信息等,從而統(tǒng)計(jì)一下活躍用戶等。那么對是、否這兩個(gè)值的操作,就可以使用 Bitmap 這個(gè)結(jié)構(gòu)進(jìn)行壓縮

Java 的 Boolean 占用的是多少位?

Java 虛擬機(jī)規(guī)范里,描述是:將 Boolean 類型映射成的是 1 和 0 兩個(gè)數(shù)字,它占用的空間是和 int 相同的 32 位。即使有的虛擬機(jī)實(shí)現(xiàn)把 Boolean 映射到了 byte 類型上,它所占用的空間,對于大量的、有規(guī)律的 Boolean 值來說,也是太大了

如代碼所示,通過判斷 int 中的每一位,它可以保存 32 個(gè) Boolean 值!

int a= 0b0001_0001_1111_1101_1001_0001_1111_1101;

Bitmap 就是使用 Bit 進(jìn)行記錄的數(shù)據(jù)結(jié)構(gòu),里面存放的數(shù)據(jù)不是 0 就是 1,Java 中的相關(guān)結(jié)構(gòu)類,就是 java.util.BitSet,BitSet 底層是使用 long 數(shù)組實(shí)現(xiàn)的,所以它的最小容量是 64

10 億的 Boolean 值,只需要 128MB 的內(nèi)存,下面既是一個(gè)占用了 256MB 的用戶性別的判斷邏輯,可以涵蓋長度為 10 億的 ID

static BitSet missSet = new BitSet(010_000_000_000); 
static BitSet sexSet = new BitSet(010_000_000_000); 
String getSex(int userId) { 
    boolean notMiss = missSet.get(userId); 
    if (!notMiss) { 
        //lazy fetch 
        String lazySex = dao.getSex(userId); 
        missSet.set(userId, true); 
        sexSet.set(userId, "female".equals(lazySex)); 
    } 
    return sexSet.get(userId) ? "female" : "male"; 
}

這些數(shù)據(jù),放在堆內(nèi)內(nèi)存中,還是過大了。幸運(yùn)的是,Redis 也支持 Bitmap 結(jié)構(gòu),如果內(nèi)存有壓力,我們可以把這個(gè)結(jié)構(gòu)放到 Redis 中,判斷邏輯也是類似的

給出一個(gè) 1GB 內(nèi)存的機(jī)器,提供 60億 int 數(shù)據(jù),如何快速判斷有哪些數(shù)據(jù)是重復(fù)的?

Bitmap 是一個(gè)比較底層的結(jié)構(gòu),在它之上還有一個(gè)叫作布隆過濾器的結(jié)構(gòu)(Bloom Filter),布隆過濾器可以判斷一個(gè)值不存在,或者可能存在

如圖,它相比較 Bitmap,它多了一層 hash 算法。既然是 hash 算法,就會有沖突,所以有可能有多個(gè)值落在同一個(gè) bit 上。它不像 HashMap一樣,使用鏈表或者紅黑樹來處理沖突,而是直接將這個(gè)hash槽重復(fù)使用。從這個(gè)特性我們能夠看出,布隆過濾器能夠明確表示一個(gè)值不在集合中,但無法判斷一個(gè)值確切的在集合中

上面這種優(yōu)化方式,本質(zhì)上也是把大對象變成小對象的方式,在軟件設(shè)計(jì)中有很多類似的思路。比如像一篇新發(fā)布的文章,頻繁用到的是摘要數(shù)據(jù),就不需要把整個(gè)文章內(nèi)容都查詢出來;用戶的 feed 信息,也只需要保證可見信息的速度,而把完整信息存放在速度較慢的大型存儲里

數(shù)據(jù)的冷熱分離

數(shù)據(jù)除了橫向的結(jié)構(gòu)緯度,還有一個(gè)縱向的時(shí)間維度,對時(shí)間維度的優(yōu)化,最有效的方式就是冷熱分離,所謂熱數(shù)據(jù),就是靠近用戶的,被頻繁使用的數(shù)據(jù);而冷數(shù)據(jù)是那些訪問頻率非常低,年代非常久遠(yuǎn)的數(shù)據(jù)

同一句復(fù)雜的 SQL,運(yùn)行在幾千萬的數(shù)據(jù)表上,和運(yùn)行在幾百萬的數(shù)據(jù)表上,前者的效果肯定是很差的。所以,雖然你的系統(tǒng)剛開始上線時(shí)速度很快,但隨著時(shí)間的推移,數(shù)據(jù)量的增加,就會漸漸變得很慢

冷熱分離是把數(shù)據(jù)分成兩份,如下圖,一般都會保持一份全量數(shù)據(jù),用來做一些耗時(shí)的統(tǒng)計(jì)操作

由于冷熱分離在工作中經(jīng)常遇到,所以面試官會頻繁問到數(shù)據(jù)冷熱分離的方案。簡單介紹三種:

1.數(shù)據(jù)雙寫

把對冷熱庫的插入、更新、刪除操作,全部放在一個(gè)統(tǒng)一的事務(wù)里面。由于熱庫(比如 MySQL)和冷庫(比如 Hbase)的類型不同,這個(gè)事務(wù)大概率會是分布式事務(wù)。在項(xiàng)目初期,這種方式是可行的,但如果是改造一些遺留系統(tǒng),分布式事務(wù)基本上是改不動(dòng)的,我通常會把這種方案直接廢棄掉

2.寫入 MQ 分發(fā)

通過 MQ 的發(fā)布訂閱功能,在進(jìn)行數(shù)據(jù)操作的時(shí)候,先不落庫,而是發(fā)送到 MQ 中。單獨(dú)啟動(dòng)消費(fèi)進(jìn)程,將 MQ 中的數(shù)據(jù)分別落到冷庫、熱庫中。使用這種方式改造的業(yè)務(wù),邏輯非常清晰,結(jié)構(gòu)也比較優(yōu)雅。像訂單這種結(jié)構(gòu)比較清晰、對順序性要求較低的系統(tǒng),就可以采用 MQ 分發(fā)的方式。但如果你的數(shù)據(jù)庫實(shí)體量非常大,用這種方式就要考慮程序的復(fù)雜性了

3.使用 Binlog 同步

針對 MySQL,就可以采用 Binlog 的方式進(jìn)行同步,使用 Canal 組件,可持續(xù)獲取最新的 Binlog 數(shù)據(jù),結(jié)合 MQ,可以將數(shù)據(jù)同步到其他的數(shù)據(jù)源中

思維發(fā)散

對于結(jié)果集的操作,我們可以再發(fā)散一下思維。可以將一個(gè)簡單冗余的結(jié)果集,改造成復(fù)雜高效的數(shù)據(jù)結(jié)構(gòu)。這個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)可以代理我們的請求,有效地轉(zhuǎn)移耗時(shí)操作

比如,我們常用的數(shù)據(jù)庫索引,就是一種對數(shù)據(jù)的重新組織、加速

B+ tree 可以有效地減少數(shù)據(jù)庫與磁盤交互的次數(shù),它通過類似 B+ tree 的數(shù)據(jù)結(jié)構(gòu),將最常用的數(shù)據(jù)進(jìn)行索引,存儲在有限的存儲空間中

還有就是,在 RPC 中常用的序列化

有的服務(wù)是采用的 SOAP 協(xié)議的 WebService,它是基于 XML 的一種協(xié)議,內(nèi)容大傳輸慢,效率低下?,F(xiàn)在的 Web 服務(wù)中,大多數(shù)是使用 json 數(shù)據(jù)進(jìn)行交互的,json 的效率相比 SOAP 就更高一些

另外,大家應(yīng)該都聽過 google 的 protobuf,由于它是二進(jìn)制協(xié)議,而且對數(shù)據(jù)進(jìn)行了壓縮,性能是非常優(yōu)越的。protobuf 對數(shù)據(jù)壓縮后,大小只有 json 的 1/10,xml 的 1/20,但是性能卻提高了 5-100 倍,protobuf 的設(shè)計(jì)是值得借鑒的,它通過 tag|leng|value 三段對數(shù)據(jù)進(jìn)行了非常緊湊的處理,解析和傳輸速度都特別快

針對大對象,我們有結(jié)構(gòu)緯度的優(yōu)化和時(shí)間維度的優(yōu)化兩種方法

  • 結(jié)構(gòu)緯度來說,通過把對象切分成合適的粒度,可以把操作集中在小數(shù)據(jù)結(jié)構(gòu)上,減少時(shí)間處理成本;通過把對象進(jìn)行壓縮、轉(zhuǎn)換,或者提取熱點(diǎn)數(shù)據(jù),就可以避免大對象的存儲和傳輸成本
  • 時(shí)間緯度來說,就可以通過冷熱分離的手段,將常用的數(shù)據(jù)存放在高速設(shè)備中,減少數(shù)據(jù)處理的集合,加快處理速度

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。 

相關(guān)文章

  • Spring?Security如何為用戶示例添加角色詳解

    Spring?Security如何為用戶示例添加角色詳解

    目前我正在用Java開發(fā)一個(gè)基于Spring Boot的web應(yīng)用程序,下面這篇文章主要給大家介紹了關(guān)于Spring?Security如何為用戶示例添加角色的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • Java的枚舉類型使用方法詳解

    Java的枚舉類型使用方法詳解

    這篇文章主要介紹了Java的枚舉類型使用方法詳解,從背景、到定義、特點(diǎn)、使用方式做個(gè)簡單了解,感興趣的小伙伴們可以參考一下
    2016-01-01
  • Java中反射的"暴破"機(jī)制(SetAccessible方法)詳解

    Java中反射的"暴破"機(jī)制(SetAccessible方法)詳解

    這篇文章主要為大家詳細(xì)介紹了Java中反射的"暴破"機(jī)制,以及如何利用這一機(jī)制實(shí)現(xiàn)訪問非公有屬性,方法,和構(gòu)造器,文中示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-08-08
  • 如何基于Spring使用工廠模式實(shí)現(xiàn)程序解耦

    如何基于Spring使用工廠模式實(shí)現(xiàn)程序解耦

    這篇文章主要介紹了如何基于Spring使用工廠模式實(shí)現(xiàn)程序解耦,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Maven優(yōu)雅的添加第三方Jar包的方法

    Maven優(yōu)雅的添加第三方Jar包的方法

    下面小編就為大家?guī)硪黄狹aven優(yōu)雅的添加第三方Jar包的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • java怎么創(chuàng)建目錄(刪除/修改/復(fù)制目錄及文件)代碼實(shí)例

    java怎么創(chuàng)建目錄(刪除/修改/復(fù)制目錄及文件)代碼實(shí)例

    這篇文章主要介紹了java怎么創(chuàng)建目錄,還包括刪除/修改/復(fù)制目錄及文件,代碼簡單,下面直接看代碼吧
    2013-12-12
  • java中的動(dòng)態(tài)代理與責(zé)任鏈模式詳解

    java中的動(dòng)態(tài)代理與責(zé)任鏈模式詳解

    這篇文章主要介紹了java中的動(dòng)態(tài)代理與責(zé)任鏈模式詳解,動(dòng)態(tài)代理提供了一種靈活且非侵入式的方式,可以對對象的行為進(jìn)行定制和擴(kuò)展,它在代碼重用、解耦和業(yè)務(wù)邏輯分離、性能優(yōu)化以及系統(tǒng)架構(gòu)中起到了重要的作用,需要的朋友可以參考下
    2023-08-08
  • springboot3生成本地文件url的實(shí)現(xiàn)示例

    springboot3生成本地文件url的實(shí)現(xiàn)示例

    本文主要介紹了springboot3生成本地文件url的實(shí)現(xiàn)示例,從而提供一種高效的文件管理方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • springboot 緩存@EnableCaching實(shí)例

    springboot 緩存@EnableCaching實(shí)例

    這篇文章主要介紹了springboot 緩存@EnableCaching實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • @Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案

    @Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案

    這篇文章主要介紹了@Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09

最新評論

沙田区| 琼中| 萍乡市| 开封市| 南阳市| 大同县| 林州市| 霸州市| 印江| 沈丘县| 安乡县| 普定县| 岳池县| 合川市| 合肥市| 宾川县| 黔东| 株洲县| 二连浩特市| 沁源县| 宜宾市| 临高县| 克什克腾旗| 东辽县| 郓城县| 亳州市| 嵩明县| 聊城市| 英超| 和政县| 任丘市| 荆州市| 潮州市| 江达县| 江达县| 朝阳市| 利川市| 朝阳县| 新兴县| 乌兰浩特市| 喀喇沁旗|