[英]How to estimate if the JVM has enough free memory for a particular data structure?
我有以下情況:有幾台機器組成一個集群。 客戶端可以加載數據集,我們需要選擇將加載數據集的節點,如果沒有一台機器可以適合數據集,則拒絕加載/避免OOM錯誤。
我們當前所做的事情:我們現在是數據集中的entry count
,並估計memory to be used
entry count * empirical factor
的memory to be used
entry count * empirical factor
(手動確定)。 然后檢查它是否低於空閑內存(由Runtime.freeMemory()
得到),如果是,則加載它(否則重做其他節點上的進程/報告沒有空閑容量)。
這種方法的問題是:
empirical factor
freeMemory
有時可能由於某些未清理的垃圾而報告不足(可以通過在每次調用之前運行System.gc
來避免,但這會降低服務器的速度並且還可能導致過早的升級) 這個問題有更好的解決方案嗎?
empirical factor
可以計算為構建步驟並放置在屬性文件中。
雖然freeMemory()
幾乎總是小於GC之后可以釋放的數量,但是如果maxMemory()
表明可能有很多,你可以檢查它是否可用並調用System.gc()
。
注意:在生產中使用System.gc()
僅在非常罕見的情況下進行,並且通常它經常被錯誤地使用,從而導致性能降低並使實際問題變得模糊。
我會避免觸發OOME,除非你正在運行的是一個JVM,你可以根據需要重啟。
我的解決方案
如果除了您的程序之外沒有其他進程在運行,請將Xmx設置為物理機RAM的90%-95%
。 對於32 GB RAM機器,將Xmx
設置為27MB - 28MB
。
使用良好的gc算法之一 - CMS或G1GC並微調相關參數。 I prefer G1GC if you need more than 4 GB RAM for your application
。 如果您選擇G1GC,請參閱此問題:
自己計算內存使用量上限而不是檢查空閑內存。 添加要分配的已用內存和內存。 Subtract it from your own cap like 90% of Xmx
。 如果仍有可用內存,則授予內存分配請求。
另一種方法是隔離自己的JVM中的每個數據負載。 您只需預定義每個JVM的最大堆大小等,並設置每個主機的JVM數量,使每個JVM可以占用其完整的最大堆大小。 這將使用更多的資源 - 這意味着你不能通過填充更多的低內存數據負載來利用每個內存的最后一個字節 - 但它大大簡化了問題(並降低了出錯的風險),它可以告訴您何時/是否需要添加新主機,最重要的是,它可以減少任何一個客戶端對所有其他客戶端的影響。
使用這種方法,給定的JVM要么“忙”,要么“可用”。
在任何給定的數據加載完成之后,相關的JVM可以聲明自己可用於新的數據加載,或者它可以關閉。 (無論哪種方式,您都希望有一個單獨的進程來監視JVM並確保正確的數字始終在運行。)
另一種方法是“只是嘗試加載數據集”(如果拋出OOM則返回)但是一旦拋出OOM,你可能會破壞在同一個JVM中運行的其他線程,並且沒有優雅的方法從中恢復。
在JVM中沒有很好的方法來處理和從OOME中恢復,但是在 OOM發生之前有辦法做出反應。 Java有java.lang.ref.SoftReference, 保證在虛擬機拋出OutOfMemoryError之前清除它 。 這個事實可以用於OOM的早期預測。 例如,如果預測被觸發,則可以中止數據加載。
ReferenceQueue<Object> q = new ReferenceQueue<>();
SoftReference<Object> reference = new SoftReference<>(new Object(), q);
q.remove();
// reference removed - stop data load immediately
可以使用-XX:SoftRefLRUPolicyMSPerMB標志(對於Oracle JVM)調整靈敏度。 解決方案並不理想,它的有效性取決於各種因素 - 代碼中使用的其他軟參考,GC如何調整,JVM版本,火星上的天氣......但如果你幸運的話它可以幫助。
正如您所正確指出的那樣,使用freeMemory
不會告訴您Java垃圾收集可以釋放的內存量。 您可以使用JVM的JConsole,VisualVM,jstat和printGCStats
選項等工具運行負載測試並了解JVM堆使用模式和內存分配, printGCStats
分配模式。 這將給出更准確地計算empirical factor
的想法,基本上了解您的java應用程序可以處理的加載模式是什么。 接下來將選擇正確的GC並調整基本GC設置以提高效率。 這不是一個快速的解決方案,但從長遠來看可能是一個更好的解決方案。
使用-XX殺死JVM的另一種方法:OnOutOfMemoryError =“kill -9%p” JVM設置,一旦發生OOM,然后寫入,就會生成一個簡單的進程監視腳本,以便在JVM關閉時啟動它。
客戶端可以加載數據集,我們需要選擇將加載數據集的節點,如果沒有一台機器可以適合數據集,則拒絕加載/避免OOM錯誤。
這是一個工作調度問題,即我有限的資源我們如何最好地利用它們。 我會在接近結束時收到OOM問題。
我們有一個主要因素,即RAM,但調度問題的解決方案取決於許多因素,即......
作業是小還是大,即在節點上運行數百/數千個或兩個或三個。 想想Linux調度程序。
他們需要在特定的時間框架內完成嗎? 實時調度程序。
鑒於我們在工作開始時所知道的一切,我們可以預測一份工作何時會在一段時間內結束? 如果我們可以預測在節點X上我們每15到20秒釋放100MB,我們就可以在該節點上安排200Mb的工作,即我相信在40秒內我將在該節點上完成200Mb的空間並且40秒是提交作業的人員或機器的可接受限制。
讓我們假設我們有如下函數。
predicted_time predict(long bytes[, factors]);
這些factors
是我們需要考慮的其他因素,我在上面提到過,對於每個應用程序,都會有一些東西可以添加以適合您的場景。
這些因素將計算時,可以任意的加權predicted_time
。
predicted_time
是(可以是任何TIMEUNIT),這點從現在開始,它可以服務於這個任務,節點給你最小的數字相信的毫秒數是工作應安排的節點。 然后,您可以使用此函數,我們有一個任務隊列,即在下面的代碼中this.nodes[i]
表示一個JVM實例。
private void scheduleTask() {
while(WorkEvent()) {
while(!this.queue.isEmpty()) {
Task t = this.queue.poll();
for (int i = 0; i < this.maxNodes; i++) {
long predicted_time = this.nodes[i].predict(t);
if (predicted_time < 0) {
boolean b = this.queue.offer(t);
assert(b);
break;
}
if (predicted_time <= USER_EXPERIENCE_DELAY) {
this.nodes[i].addTask(t);
break;
}
alert_user(boolean b = this.queue.offer(t);
assert(b);
}
}
}
}
如果predicted_time < 0
我們有錯誤,我們重新安排工作,實際上我們想知道為什么,但這並不難添加。 如果predicted_time <= USER_EXPERIENCE_DELAY
,則可以安排作業。
這是如何避免OOM的
我們可以從我們的調度程序中收集我們想要的任何統計數據,即正確安排的X大小的工作量,目標是減少錯誤並使其隨着時間的推移更可靠,即減少我們告訴客戶他們的工作的次數無法提供服務。 我們所做的就是將問題減少到我們可以在統計上改進的最佳解決方案。
客戶端可以加載數據集,我們需要選擇將加載數據集的節點,如果沒有一台機器可以適合數據集,則拒絕加載/避免OOM錯誤。
這是一個工作調度問題,即我有限的資源我們如何最好地利用它們。 我會在接近結束時收到OOM問題。
我們有一個主要因素,即RAM,但調度問題的解決方案取決於許多因素,即......
作業是小還是大,即在節點上運行數百/數千個或兩個或三個。 想想Linux調度程序。
他們需要在特定的時間框架內完成嗎? 實時調度程序。
鑒於我們在工作開始時所知道的一切,我們可以預測一份工作何時會在一段時間內結束? 如果我們可以預測在節點X上我們每15到20秒釋放100MB,我們就可以在該節點上安排200Mb的工作,即我相信在40秒內我將在該節點上完成200Mb的空間並且40秒是提交作業的人員或機器的可接受限制。
讓我們假設我們有如下函數。
predicted_time predict(long bytes[, factors]);
這些factors
是我們需要考慮的其他因素,我在上面提到過,對於每個應用程序,都會有一些東西可以添加以適應您的場景,即有多少因素取決於您。
這些因素將計算時,可以任意的加權predicted_time
。
predicted_time
是(可以是任何TIMEUNIT),這點從現在開始,它可以服務於這個任務,節點給你最小的數字相信的毫秒數是工作應安排的節點。 然后,您可以使用此函數,我們有一個任務隊列,即在下面的代碼中this.nodes[i]
表示一個JVM實例。
private void scheduleTask() {
while(WorkEvent()) {
while(!this.queue.isEmpty()) {
Task t = this.queue.poll();
for (int i = 0; i < this.maxNodes; i++) {
long predicted_time = this.nodes[i].predict(t);
if (predicted_time < 0) {
boolean b = this.queue.offer(t);
assert(b);
break;
}
if (predicted_time <= USER_EXPERIENCE_DELAY) {
this.nodes[i].addTask(t);
break;
}
alert_user(boolean b = this.queue.offer(t);
assert(b);
}
}
}
}
如果predicted_time < 0
我們有錯誤,我們重新安排工作,實際上我們想知道為什么,但這並不難添加。 如果predicted_time <= USER_EXPERIENCE_DELAY
,則可以安排作業。
這是如何避免OOM的
我們可以從我們的調度程序中收集我們想要的任何統計數據,即正確安排的X大小的工作量,目標是減少錯誤並使其隨着時間的推移更可靠,即減少我們告訴客戶他們的工作的次數無法維修或失敗。 我們已經或至少試圖嘗試的是將問題減少到我們可以在實現最佳解決方案時進行統計改進的問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.