搜尋
首頁Javajava教程降低java垃圾回收開銷的5個建議

保持GC低開銷的竅門有哪些?

隨著一再拖延而即將發布的 Java9,G1(“Garbage First”)垃圾回收器將被成為 HotSpot 虛擬機默認的垃圾回收器。從 serial 垃圾回收器到CMS 收集器, JVM 見證了許多 GC 實現,而 G1 將成為其下一代垃圾回收器。

隨著垃圾收集器的發展,每一代 GC 與其上一代相比,都帶來了巨大的進步和改善。 parallel GC 與 serial GC 相比,它讓垃圾收集器以多執行緒的方式運作,充分利用了多核心電腦的運算能力。 CMS(“Concurrent Mark-Sweep”)收集器與parallel GC 相比,它將回收過程分成了多個階段,使得應用線程正在運行的時候,收集工作可以並發地完成,大大改善了頻繁執行“stop- the-world” 的情況。 G1 對於擁有大量堆疊記憶體的 JVM 表現出更好的性能,並且具有更好的可預測和統一的暫停過程。

Tip #1: 預測集合的容量

所有標準的 Java 集合,包括定制和擴展的實現(比如 Trove 和 Google 的 Guava),底層都使用了數組(原生資料類型或基於對象的類型)。因為數組一旦被分配,其大小就不可變,因此當添加元素到集合時,大多數情況下都會導致需要重新申請一個新的大容量數組替換老的數組(指集合底層實現使用的數組)。

即使沒有提供集合初始化的大小,大多數集合的實作都盡量優化重新分配數組的處理並且將其開銷平攤到最低。不過,在構造集合的時候就提供大小可以得到最佳的效果。

讓我們將下面的程式碼作為一個簡單的例子分析一下:

public static List reverse(List & lt; ? extends T & gt; list) {    List result = new ArrayList();
    for (int i = list.size() - 1; i & gt; = 0; i--) {
        result.add(list.get(i));
    }    return result;
}

This method allocates a new array, then fills it up with items from another list, only in reverse order. 這個方法分配了一個新的數組,然後用另一個list 中元素對該數組進行填充,只是元素的數序改變了。

這個處理方式可能會付出慘痛的效能代價,其最佳化的點在新增元素到新的 list 中這行程式碼。 隨著每一次添加元素,list 都需要確保其底層數組擁有足夠的位置來容納新的元素。如果有空閒的位置,那麼只是簡單地將新元素儲存到下一個空閒的插槽。如果沒有的話,將分配一個新的底層數組,將其拷貝舊的數組內容分配到新的數組中,然後添加新的元素。這將導致多次分配數組,那些剩餘的舊數組最終被 GC 回收。

我們可以透過在建構集合時讓其底層的陣列知道它將儲存多少元素,從而避免這些多餘的分配

public static List reverse(List & lt; ? extends T & gt; list) {    List result = new ArrayList(list.size());
    for (int i = list.size() - 1; i & gt; = 0; i--) {
        result.add(list.get(i));
    }    return result;
}

上面的程式碼透過ArrayList 的建構器指定足夠大的空間來儲存list.size()個元素,在初始化時完成分配的執行,這意味著List 在迭代的過程中無需再次分配記憶體。

Guava 的集合類別則更進一步,允許初始化集合時明確指定期望元素的個數或指定一個預測值。

List result = Lists.newArrayListWithCapacity(list.size());List result = Lists.newArrayListWithExpectedSize(list.size());

上面的程式碼中,前者用於我們已經準確地知道集合將要儲存多少元素,而後者的分配方式考慮了錯誤預估的情況。

Tip #2:直接處理資料流

當處理資料流時,例如從一個檔案讀取資料或從網路下載數據,下面的程式碼是非常常見的:

byte[] fileData = readFileToByteArray(new File("myfile.txt"));

所產生的位元組數組可能被解析XML 文件、JSON 物件或協定緩衝訊息,以及一些常見的可選項。

當處理大檔案或檔案的大小無法預測時,上面的做法很是不明智的,因為當 JVM 無法分配一個緩衝區來處理真正檔案時,就會導致OutOfMemeoryErrors。

即使資料的大小是可管理的,當到垃圾回收時,使用上面的模式依然會造成巨大的開銷,因為它在堆中分配了一塊非常大的區域來儲存檔案資料。

一種更好的處理方式是使用合適的 InputStream (例如在這個例子中使用 FileInputStream)直接傳遞給解析器,不再一次性將整個文件讀取到一個位元組數組中。所有主流的開源函式庫都提供對應的 API 來直接接受一個輸入流進行處理,例如:

FileInputStream fis = new FileInputStream(fileName);
MyProtoBufMessage msg = MyProtoBufMessage.parseFrom(fis);

Tip #3: 使用不可變的物件

不變性有太多的好處。甚至不用我贅述什麼。然而,有一個優點會對垃圾回收產生影響,應該要關心一下。

一個不可變物件的屬性在物件被創建後就不能被修改(在這裡的例子使用的是引用資料類型的屬性),例如:

public class ObjectPair {
    private final Object first;    
    private final Object second;    
    
    public ObjectPair(Object first, Object second) {        
       this.first = first;        
       this.second = second;
    }    
    public Object getFirst() {        
        return first;
    }    
    public Object getSecond() {        
        return second;
    }
}

將上面的類別實例化後會產生一個不可變對象—它的所有屬性用final 修飾,構造完成後就不能改變了。

不可变性意味着所有被一个不可变容器所引用的对象,在容器构造完成前对象就已经被创建。就 GC 而言:这个容器年轻程度至少和其所持有的最年轻的引用一样。这意味着当在年轻代执行垃圾回收的过程中,GC 因为不可变对象处于老年代而跳过它们,直到确定这些不可变对象在老年代中不被任何对象所引用时,才完成对它们的回收。

更少的扫描对象意味着对内存页更少的扫描,越少的扫描内存页就意味着更短的 GC 生命周期,也意味着更短的 GC 暂停和更好的总吞吐量。

Tip #4: 小心字符串拼接

字符串可能是在所有基于 JVM 应用程序中最常用的非原生数据结构。然而,由于其隐式地开销负担和简便的使用,非常容易成为占用大量内存的罪归祸首。

这个问题很明显不在于字符串字面值,而是在运行时分配内存初始化产生的。让我们快速看一下动态构建字符串的例子:

public static String toString(T[] array) {    
   String result = "[";    
   for (int i = 0; i & lt; array.length; i++) {
        result += (array[i] == array ? "this" : array[i]);        
        if (i & lt; array.length - 1) {
            result += ", ";
        }
    }
    result += "]";
    return result;
}

这是个看似不错的方法,接收一个字符数组然后返回一个字符串。但是这对于对象内存分配却是灾难性的。

很难看清这语法糖的背后,但是幕后的实际情况是这样的:

public static String toString(T[] array) {    
     String result = "[";    
     for (int i = 0; i & lt; array.length; i++) {
        StringBuilder sb1 = new StringBuilder(result);
        sb1.append(array[i] == array ? "this" : array[i]);
        result = sb1.toString();        
        
        if (i & lt; array.length - 1) {
            StringBuilder sb2 = new StringBuilder(result);
            sb2.append(", ");
            result = sb2.toString();
        }
    }
    StringBuilder sb3 = new StringBuilder(result);
    sb3.append("]");
    result = sb3.toString();
    return result;
}

字符串是不可变的,这意味着每发生一次拼接时,它们本身不会被修改,而是依次分配新的字符串。此外,编译器使用了标准的 StringBuilder 类来执行这些拼接操作。这就会有问题了,因为每一次迭代,既隐式地分配了一个临时字符串,又隐式分配了一个临时的 StringBuilder 对象来帮助构建最终的结果。

最佳的方式是避免上面的情况,使用 StringBuilder 和直接的追加,以取代本地拼接操作符(“+”)。下面是一个例子:

public static String toString(T[] array) {
    StringBuilder sb = new StringBuilder("[");    
    for (int i = 0; i & lt; array.length; i++) {
        sb.append(array[i] == array ? "this" : array[i]);        
        if (i & lt; array.length - 1) {
            sb.append(", ");
        }
    }
    sb.append("]");    
    return sb.toString();
}

这里,我们只在方法开始的时候分配了唯一的一个 StringBuilder。至此,所有的字符串和 list 中的元素都被追加到单独的一个StringBuilder中。最终使用 toString() 方法一次性将其转成成字符串返回。

Tip #5: 使用特定的原生类型的集合

Java 标准的集合库简单且支持泛型,允许在使用集合时对类型进行半静态地绑定。比如想要创建一个只存放字符串的 Set 或者存储 Map

TIntDoubleMap map = new TIntDoubleHashMap();
map.put(5, 7.0);
map.put(-1, 9.999);...

Trove 的底层实现使用了原生类型的数组,所以当操作集合的时候不会发生元素的装箱(int->Integer)或者拆箱(Integer->int), 没有存储对象,因为底层使用原生数据类型存储。

最后

随着垃圾收集器持续的改进,以及运行时的优化和 JIT 编译器也变得越来越智能。我们作为开发者将会发现越来越少地考虑如何编写 GC 友好的代码。然而,就目前阶段,不论 G1 如何改进,我们仍然有很多可以做的事来帮 JVM 提升性能。


陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。