日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

减少GC开销的5个编码技巧

發(fā)布時(shí)間:2023/12/10 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 减少GC开销的5个编码技巧 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在這篇文章中,我們來(lái)了解一下讓代碼變得高效的五種技巧,這些技巧可以使我們的垃圾收集器(GC)在分配內(nèi)存以及釋放內(nèi)存上面,占用更少的CPU時(shí)間,減少GC的開(kāi)銷(xiāo)。當(dāng)內(nèi)存被回收的時(shí)候,GC處理很長(zhǎng)時(shí)間經(jīng)常會(huì)導(dǎo)致我們的代碼中斷(又叫做”stop the world”)。

背景

GC用來(lái)處理大量的短期的對(duì)象的分配(試想打開(kāi)一個(gè)web頁(yè)面,一旦頁(yè)面被加載之后,被分配內(nèi)存的大部分對(duì)象都會(huì)被廢棄)。

GC使用一個(gè)被稱(chēng)作”新生代”堆空間來(lái)完成這件事情?!毙律笔怯脕?lái)存放新建對(duì)象的堆內(nèi)存。每一個(gè)對(duì)象都有一個(gè)”age”(存儲(chǔ)在對(duì)象的頭信息中),用來(lái)定義存放很多沒(méi)有被回收的垃圾集合。一旦一個(gè)確定的”age”到達(dá),對(duì)象就會(huì)被復(fù)制到堆中的另一塊空間,這個(gè)空間被稱(chēng)作”幸存者空間”或者”老年代空間”。(譯者注:實(shí)際上幸存者空間位于新生代空間中,原文有誤,不過(guò)這里暫時(shí)按照原文來(lái)翻譯,更詳細(xì)的內(nèi)容請(qǐng)點(diǎn)擊成為JavaGC專(zhuān)家Part I — 深入淺出Java垃圾回收機(jī)制)

雖然這樣很有效,但是還是有很大代價(jià)的。減少臨時(shí)分配的數(shù)量確實(shí)可以幫助我們?cè)黾油掏铝?#xff0c;尤其是在大規(guī)模數(shù)據(jù)的環(huán)境下,或者資源有限制的app中。

下面的五種代碼方式可以更加有效的利用內(nèi)存,并且不需要花費(fèi)很多的時(shí)間,也不會(huì)降低代碼可讀性。

1、避免隱式的String字符串

String字符串是我們管理的每一個(gè)數(shù)據(jù)結(jié)構(gòu)中不可分割的一部分。它們?cè)诒环峙浜昧酥蟛豢梢员恍薷?。比如?#43;”操作就會(huì)分配一個(gè)鏈接兩個(gè)字符串的新的字符串。更糟糕的是,這里分配了一個(gè)隱式的StringBuilder對(duì)象來(lái)鏈接兩個(gè)String字符串。

例如:

1 a = a + b; // a and b are Strings

編譯器在背后就會(huì)生成這樣的一段兒代碼:

1 2 3 4 StringBuilder temp = new StringBuilder(a). temp.append(b); a = temp.toString(); // 一個(gè)新的 String 對(duì)象被分配 // 第一個(gè)對(duì)象 “a” 現(xiàn)在可以說(shuō)是垃圾了

它變得更糟糕了。

讓我們來(lái)看這個(gè)例子:

1 2 3 String result = foo() + arg; result += boo(); System.out.println(“result = “ + result);

在這個(gè)例子中,背后有三個(gè)StringBuilders 對(duì)象被分配 – 每一個(gè)都是”+”的操作所產(chǎn)生,和兩個(gè)額外的String對(duì)象,一個(gè)持有第二次分配的result,另一個(gè)是傳入到print方法的String參數(shù),在看似非常簡(jiǎn)單的一段語(yǔ)句中有5個(gè)額外的對(duì)象。

試想一下在實(shí)際的代碼場(chǎng)景中會(huì)發(fā)生什么,例如,通過(guò)xml或者文件中的文本信息生成一個(gè)web頁(yè)面的過(guò)程。在嵌套循環(huán)結(jié)構(gòu),你將會(huì)發(fā)現(xiàn)有成百上千的對(duì)象被隱式的分配了。盡管VM有處理這些垃圾的機(jī)制,但還是有很大代價(jià)的 – 代價(jià)也許由你的用戶(hù)來(lái)承擔(dān)。

解決方案:

減少垃圾對(duì)象的一種方式就是善于使用StringBuilder 來(lái)建對(duì)象,下面的例子實(shí)現(xiàn)了與上面相同的功能,然而僅僅生成了一個(gè)StringBuilder 對(duì)象,和一個(gè)存儲(chǔ)最終result 的String對(duì)象。

1 2 3 StringBuilder value = new StringBuilder(“result = “); value.append(foo()).append(arg).append(boo()); System.out.println(value);

通過(guò)留心String和StringBuilder被隱式分配的可能,可以減少分配的短期的對(duì)象的數(shù)量,尤其在有大量代碼的位置。

2、計(jì)劃好List的容量

像ArrayList這樣的動(dòng)態(tài)集合用來(lái)存儲(chǔ)一些長(zhǎng)度可變化數(shù)據(jù)的基本結(jié)構(gòu)。ArrayList和一些其他的集合(如HashMap、TreeMap),底層都是通過(guò)使用Object[]數(shù)組來(lái)實(shí)現(xiàn)的。而String(它們自己包裝在char[]數(shù)組中),char數(shù)組的大小是不變的。那么問(wèn)題就出現(xiàn)了,如果它們的大小是不變的,我們?cè)趺茨芊舏tem記錄到集合中去呢?答案顯而易見(jiàn):分配更多的數(shù)組。

看下面的例子:

1 2 3 4 5 6 7 List<Item> items = new ArrayList<Item>(); ?? for (int i = 0; i < len; i++) { Item item = readNextItem(); items.add(item); }

len的值決定了循環(huán)結(jié)束時(shí)items 最終的大小。然而,最初,ArrayList的構(gòu)造器并不知道這個(gè)值的大小,構(gòu)造器會(huì)分配一個(gè)默認(rèn)的Object數(shù)組的大小。一旦內(nèi)部數(shù)組溢出,它就會(huì)被一個(gè)新的、并且足夠大的數(shù)組代替,這就使之前分配的數(shù)組成為了垃圾。

如果執(zhí)行數(shù)千次的循環(huán),那么就會(huì)進(jìn)行更多次數(shù)的新數(shù)組分配操作,以及更多次數(shù)的舊數(shù)組回收操作。對(duì)于在大規(guī)模環(huán)境下運(yùn)行的代碼,這些分配和釋放的操作應(yīng)該盡可能從CPU周期中剔除。

解決方案:

無(wú)論什么時(shí)候,盡可能的給List或者M(jìn)ap分配一個(gè)初始容量,就像這樣:

1 List<MyObject> items = new ArrayList<MyObject>(len);

因?yàn)長(zhǎng)ist初始化,有足夠的容量,所有這樣可以減少內(nèi)部數(shù)組在運(yùn)行時(shí)不必要的分配和釋放。如果你不知道確定的大小,最好估算一下這個(gè)值的平均值,添加一些緩沖,防止意外溢出。

3、使用高效的含有原始類(lèi)型的集合

當(dāng)前版本的Java編譯器對(duì)于含有基本數(shù)據(jù)類(lèi)型的鍵的數(shù)組以及Map的支持,是通過(guò)“裝箱”來(lái)實(shí)現(xiàn)的 – 自動(dòng)裝箱就是將原始數(shù)據(jù)裝入一個(gè)對(duì)應(yīng)的對(duì)象中,這個(gè)對(duì)象可被GC分配和回收。

這個(gè)會(huì)有一些負(fù)面的影響。Java可以通過(guò)使用內(nèi)部數(shù)組實(shí)現(xiàn)大多數(shù)的集合。對(duì)于每一條被添加到HashMap中的key/value記錄,都會(huì)分配一個(gè)存儲(chǔ)key和value的內(nèi)部對(duì)象。當(dāng)處理map的時(shí)候非??膳?#xff0c;這意味著,每當(dāng)你放一條記錄到map中的時(shí)候,就會(huì)有一次額外的分配和釋放操作發(fā)生。這很可能導(dǎo)致數(shù)量過(guò)大,而不得不重新分配新的內(nèi)部數(shù)組。當(dāng)處理有成百上千條甚至更多記錄的Map時(shí),這些內(nèi)部分配的操作將會(huì)使GC的成本增加。

一種常見(jiàn)的情況就是保存一個(gè)原始類(lèi)型(如id)和一個(gè)對(duì)象之間的映射。由于Java的HashMap設(shè)計(jì)只能包含對(duì)象類(lèi)型(而非原始類(lèi)型),這意味著,每個(gè)map的插入操作都可能分配一個(gè)額外的對(duì)象來(lái)存儲(chǔ)原始類(lèi)型(即裝箱)。

Integer.valueOf 方法緩存在-128 – 127之間的數(shù)值,但是對(duì)于范圍之外的每一個(gè)數(shù)值,除了內(nèi)部的key/value記錄對(duì)象之外,一個(gè)新的對(duì)象也將會(huì)分配。這很可能超過(guò)了GC對(duì)于map三倍的開(kāi)銷(xiāo)。對(duì)于一個(gè)C++開(kāi)發(fā)者來(lái)說(shuō),這真是讓人不安的消息,在C++中,STL 模板可以非常高效地解決這樣的問(wèn)題。

很幸運(yùn),這個(gè)問(wèn)題將會(huì)在Java的下一個(gè)版本得到解決。到那時(shí),這將會(huì)被一些提供基本的樹(shù)形結(jié)構(gòu)(Tree)、映射(Map),以及List等Java的基本類(lèi)型的庫(kù)迅速處理。我強(qiáng)力推薦Trove,我已經(jīng)使用很長(zhǎng)時(shí)間了,并且它在處理大規(guī)模的代碼時(shí)真的可以減小GC的開(kāi)銷(xiāo)。

4、使用數(shù)據(jù)流(Streams)代替內(nèi)存緩沖區(qū)(in-memory buffers)

在服務(wù)器應(yīng)用程序中,我們操作的大多數(shù)的數(shù)據(jù)都是以文件或者是來(lái)自另一個(gè)web服務(wù)器或DB的網(wǎng)絡(luò)數(shù)據(jù)流的形式呈現(xiàn)給我們。大多數(shù)情況下,傳入的數(shù)據(jù)都是序列化的形式,在我們使用它們之前需要被反序列化成Java對(duì)象。這個(gè)過(guò)程非常容易產(chǎn)生大量的隱式分配。

最簡(jiǎn)單的做法就是通過(guò)ByteArrayInputStream,ByteBuffer 把數(shù)據(jù)讀入內(nèi)存中,然后再進(jìn)行反序列化。

這是一個(gè)糟糕的舉動(dòng),因?yàn)橥暾臄?shù)據(jù)在構(gòu)造新的對(duì)象的時(shí)候,你需要為其分配空間,然后立刻又釋放空間。并且,由于數(shù)據(jù)的大小你又不知道,你只能猜測(cè) – 當(dāng)超過(guò)初始化容量的時(shí)候,不得不分配和釋放byte[]數(shù)組來(lái)存儲(chǔ)數(shù)據(jù)。

解決方案非常簡(jiǎn)單。像Java自帶的序列化工具以及Google的Protocol Buffers等,它們可以將來(lái)自于文件或網(wǎng)絡(luò)流的數(shù)據(jù)進(jìn)行反序列化,而不需要保存到內(nèi)存中,也不需要分配新的byte數(shù)組來(lái)容納增長(zhǎng)的數(shù)據(jù)。如果可以的話,你可以將這種方法和加載數(shù)據(jù)到內(nèi)存的方法比較一下,相信GC會(huì)很感謝你的。

5、List集合

不變性是很美好的,但是在大規(guī)模情境下,它就會(huì)有嚴(yán)重的缺陷。當(dāng)傳入一個(gè)List對(duì)象到方法中的情景。

當(dāng)方法返回一個(gè)集合,通常會(huì)很明智的在方法中創(chuàng)建一個(gè)集合對(duì)象(如ArrayList),填充它,并以不變的集合的形式返回。

有些情況下,這并不會(huì)得到很好的效果。最明顯的就是,當(dāng)來(lái)自多個(gè)方法的集合調(diào)用一個(gè)final集合。因?yàn)椴蛔冃?#xff0c;在大規(guī)模數(shù)據(jù)情況下,會(huì)分配大量的臨時(shí)集合。

這種情況的解決方案將不會(huì)返回新的集合,而是通過(guò)使用單獨(dú)的集合當(dāng)做參數(shù)傳入到那些方法代替組合的集合。

例子1(低效率):

1 2 3 4 5 6 List<Item> items = new ArrayList<Item>(); for (FileData fileData : fileDatas) { // 每一次調(diào)用都會(huì)創(chuàng)建一個(gè)存儲(chǔ)內(nèi)部臨時(shí)數(shù)組的臨時(shí)的列表 items.addAll(readFileItem(fileData)); }

例子2:

1 2 3 4 5 6 7 List<Item> items = new ArrayList<Item>(fileDatas.size() * avgFileDataSize * 1.5); ?? for (FileData fileData : fileDatas) { readFileItem(fileData, items); // 在內(nèi)部添加記錄 }

在例子2中,當(dāng)違反不變性規(guī)則的時(shí)候(這通常應(yīng)該被遵守),可以節(jié)省N個(gè)list的分配(以及任何臨時(shí)數(shù)組的分配)。這將是對(duì)你GC的一個(gè)大大的優(yōu)惠。

轉(zhuǎn)載于:https://www.cnblogs.com/xianDan/p/4846820.html

總結(jié)

以上是生活随笔為你收集整理的减少GC开销的5个编码技巧的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。