堆上与堆外的内存使用情况
總覽
最近有人問我在Java中使用堆內存的好處和智慧。 面臨相同選擇的其他人可能會對這些答案感興趣。
堆外內存沒什么特別的。 線程堆棧,應用程序代碼,NIO緩沖區都在堆外。 實際上,在C和C ++中,您只有非托管內存,因為默認情況下它沒有托管堆。 Java中托管內存或“堆”的使用是該語言的一個特殊功能。 注意:Java不是執行此操作的唯一語言。
新的Object()vs對象池vs Off堆內存
新的Object()
在Java 5.0之前,使用對象池非常流行。 創建對象仍然非常昂貴。 但是,從Java 5.0開始,對象分配和垃圾清理變得便宜得多,并且開發人員發現,通過刪除對象池并僅在需要時創建新對象,它們可以提高性能,并簡化代碼。 在Java 5.0之前,幾乎所有對象池(甚至是使用對象的對象池)都提供了改進,從Java 5.0來看,只有昂貴的對象才有意義,例如線程,套接字和數據庫連接。
對象池
在低延遲空間中,顯而易見的是,通過減少對CPU緩存的壓力,回收可變對象可以提高性能。 這些對象必須具有簡單的生命周期和簡單的結構,但是使用它們可以看到性能和抖動方面的顯著改善。
使用對象池的另一個有意義的領域是當使用許多重復的對象加載大量數據時。 隨著內存使用量的顯著減少以及GC必須管理的對象數量的減少,您看到了GC時間的減少和吞吐量的增加。
與使用同步HashMap相比,這些對象池的重量更輕,因此它們仍然可以提供幫助。
以這個StringInterner類為例。 您將所需文本的可循環使用的可變StringBuilder作為字符串傳遞給它,它將提供匹配的字符串。 傳遞String效率不高,因為您已經創建了對象。 StringBuilder可以回收。
注意:此結構具有一個有趣的屬性,除了最低Java保證所提供的功能外,不需要任何其他線程安全功能(例如volatile或sync)。 也就是說,您可以正確地看到String中的final字段,并且只能讀取一致的引用。
public class StringInterner {private final String[] interner;private final int mask;public StringInterner(int capacity) {int n = Maths.nextPower2(capacity, 128);interner = new String[n];mask = n - 1;}private static boolean isEqual(@Nullable CharSequence s, @NotNull CharSequence cs) {if (s == null) return false;if (s.length() != cs.length()) return false;for (int i = 0; i < cs.length(); i++)if (s.charAt(i) != cs.charAt(i))return false;return true;}@NotNullpublic String intern(@NotNull CharSequence cs) {long hash = 0;for (int i = 0; i < cs.length(); i++)hash = 57 * hash + cs.charAt(i);int h = (int) Maths.hash(hash) & mask;String s = interner[h];if (isEqual(s, cs))return s;String s2 = cs.toString();return interner[h] = s2;} }堆外內存使用率
使用堆外內存和使用對象池都有助于減少GC暫停,這是它們唯一的相似之處。 對象池適用于短暫的可變對象,創建對象的開銷很大,而存在很多重復的永久對象則是不可變的。 中度活潑的可變對象或復雜的對象更有可能由GC處理。 但是,中型到長壽命的可變對象受堆內存解決的許多方式的困擾。
堆外內存提供;
- 可擴展至大型內存,例如超過1 TB且大于主內存。
- 名義上對GC暫停時間的影響。
- 在進程之間共享,減少JVM之間的重復,并使分裂JVM更容易。
- 持久性,可以更快地重新啟動或回復測試中的生產數據。
在設計系統方面,堆外內存的使用為您提供了更多選擇。 最重要的改進不是性能,而是確定性。
堆外和測試
高性能計算中的最大挑戰之一是再現難以理解的錯誤,并能夠證明已解決了這些錯誤。 通過以持久方式將所有輸入事件和數據存儲在堆外,您可以將關鍵系統變成一系列復雜的狀態機。 (或者在簡單的情況下,只有一個狀態機)以這種方式,您可以在測試和生產之間獲得可重現的行為和性能。
許多投資銀行都使用此技術對一天中的任何事件可靠地重播系統,并確切地說明了該事件按原樣處理的原因。 更重要的是,一旦你有了一個位置,你可以證明你有固定發生在生產,而不是發現問題,并希望這是問題的問題。
確定性行為與確定性行為一起出現。 在測試環境中,您可以按照實際的時間重播事件,并顯示期望在生產中獲得的延遲分布。 如果硬件不同,則無法重現某些系統抖動,但是從統計角度看,您可能會非常接近。 為了避免花一天時間重放一天的數據,您可以添加一個閾值。 例如,如果事件之間的時間超過10毫秒,則您可能只能等待10毫秒。 這樣一來,您可以在不到一個小時的時間內按實際時間重播一天的事件,并查看您的更改是否改善了延遲分配。
通過降低級別,您不會失去“一次編譯,隨處運行”的某些功能嗎?
在某種程度上,這是正確的,但遠沒有您想像的要多。 當您靠近處理器工作時,您將更加依賴處理器或OS的行為方式。 幸運的是,大多數系統使用AMD / Intel處理器,就其提供的低級別保證而言,甚至ARM處理器也變得更加兼容。 操作系統之間也存在差異,并且這些技術在Linux上比Windows上更有效。 但是,如果您在MacOSX或Windows上進行開發并使用Linux進行生產,則應該沒有任何問題。 這就是我們在高頻交易中所做的。
我們通過使用堆產生了哪些新問題?
沒有什么是免費的,堆外情況就是這樣。 堆外的最大問題是您的數據結構變得不太自然。 您或者需要一個可以直接映射到堆外的簡單數據結構,或者需要一個序列化和反序列化的復雜數據結構以使其脫離堆。 顯然使用序列化有其自身的麻煩和性能損失。 因此,使用序列化要比在堆對象上慢得多。
在金融世界中,最昂貴的數據結構是扁平且簡單的,充滿了原語,可以很好地在堆外映射,幾乎沒有開銷。 但是,這并不適用于所有應用程序,您可以獲得復雜的嵌套數據結構,例如圖形,最終還必須在堆上緩存某些對象。
另一個問題是,JVM限制了您可以使用的系統數量。 您不必擔心JVM會使系統過載太多。 有了堆外處理,就解除了一些限制,您可以使用比主內存大得多的數據結構,并且開始擔心如果要這樣做,您將擁有哪種磁盤子系統。 例如,您不希望分頁至具有80 IOPS的HDD,而您可能希望擁有80,000 IOPS(每秒輸入/輸出操作)或更高(即快1000倍)的SSD。
OpenHFT如何提供幫助?
OpenHFT有許多庫可以隱藏您實際上正在使用本機內存來存儲數據的事實。 這些數據結構是持久的,幾乎沒有垃圾就可以使用。 這些用于不需少量收集即可全天運行的應用程序中
編年史隊列 -持久的事件隊列。 支持同一臺機器上跨JVM的并發編寫器,以及跨機器并發讀取器。 微秒級的延遲和每秒數百萬條消息的持續吞吐量。
編年史地圖 –鍵值地圖的本機或持久存儲。 可以在同一臺機器上的JVM之間共享,通過UDP或TCP復制和/或通過TCP遠程訪問。 微秒級延遲和持續的讀/寫速率,每臺機器每秒可進行數百萬次操作。
線程親和性 –將關鍵線程綁定到隔離的內核或邏輯CPU,以最大程度地減少抖動。 可以將抖動降低1000倍。
使用哪個API?
如果您需要記錄每個事件->編年史隊列
如果您只需要最新結果作為唯一鍵->編年史地圖
如果您關心20微秒抖動->線程親和力
結論
堆外內存可能會帶來挑戰,但也會帶來很多好處。 您可以在其中看到最大的收益,并與其他為實現可伸縮性而引入的解決方案進行比較。 與在堆緩存,消息傳遞解決方案或進程外數據庫上使用分區/分片相比,堆外可能更簡單,更快。 通過提高速度,您可能會發現不再需要為獲得所需性能所需的一些技巧。 例如,堆外解決方案可以支持對OS的同步寫入,而不必以異步方式執行寫入操作,而存在數據丟失的風險。
但是,最大的收益就是啟動時間,從而使您的生產系統重新啟動的速度更快。 例如,映射到1 TB數據集中可能需要10毫秒,并且通過重播每個事件以使您每次都獲得相同的行為,可以簡化測試的可重復性。 這使您可以創建可以依靠的質量體系。
翻譯自: https://www.javacodegeeks.com/2014/12/on-heap-vs-off-heap-memory-usage.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的堆上与堆外的内存使用情况的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: r4风险等级是什么意思?
- 下一篇: 我最喜欢的Java拼图2 + 1 = 4