1. 程式人生 > >java特種兵讀書筆記(3-5)——java程式設計師的OS之OOM

java特種兵讀書筆記(3-5)——java程式設計師的OS之OOM

HeapSize OOM

public static void main(String[] args) {
List<String> list = Lists.newArrayList();
while (true) {
list.add("hello");
}
}

會拋如下異常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:213)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
at java.util.ArrayList.add(ArrayList.java:411)
at oscar.test.oom.TOom.main(TOom.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

這是java堆空間的溢位,也就是說Old區剩餘的記憶體,已經無法滿足要晉升到Old區物件的大小了。

OOM原因

程式碼問題:

一般記憶體洩露不會這麼顯而易見。

可能有很多物件有較長的生命週期,或者在全域性區域增加一條資料導致隱藏的資料膨脹,或者死迴圈寫入資料。這種時候就需要改程式碼。

其他問題:

由於併發導致記憶體無法被GC,或者說很多物件還有引用,即物件所在宣告週期內的程式碼還沒有執行完。這時候想到的方法就是“提速”。程式碼提速了,相同的物件宣告週期會更加短暫,會很快被當做垃圾回收。

強制賦值null:

如果一個佔據很大記憶體的物件,在後續的宣告週期中沒有用途,但後續宣告週期依然很長(比如後面會做一些IO操作),這時可以用Object=null來幫助GC。

不提倡大部分程式碼都這樣寫!當方法脫離作用域後,相應的區域性變數會自動被登出掉,只有它引用的物件後續有較長的宣告週期,且物件佔用空間較大時(達到KB級別),才有必要這樣做,否則程式碼很不乾淨。

public void method() {

List<String> list = getList();//該方法返回一個超級大的list

list = null;//後面對list沒有任何操作了,而且後面有個操作時間很長,這時候幫助gc

// do something

Thread.sleep(10000);

// 如果不幫助gc,當該方法脫離作用域後,該list區域性變數才會被自動登出,多存在了10秒鐘

}

配置:

當代碼無法優化,程式跑的很快時候還發生OOM,這時就考慮改配置。最需要改的是堆的大小,將堆的空間設定的最後大,來承載更多時間單位內併發所用到的記憶體區域。

這個需要一個平衡,很大的記憶體不容易GC,但是如果很大的記憶體發生GC會很恐怖。

不停GC,GC很慢

這個時候的記憶體洩露很難發現。

因為絕大多數物件是活著的,GC過程標記活著的物件很耗時。對壓縮環節來說,因為存在大量存活物件,空隙變得很小,壓縮時間會變長。

這個時候系統表現出來的是,不停做FullGC,每次GC釋放一點點記憶體,馬上又滿了,不斷反覆。當次數達到一定量,並且平均FullGC時間達到一定比例,會報錯:OutOfMemory:GC over head limite exceed。

Sun 官方對此的定義是:"並行/併發回收器在GC回收時間過長時會丟擲OutOfMemroyError。過長的定義是,超過98%的時間用來做GC並且回收了不到2%的堆記憶體。用來避免記憶體過小造成應用不能正常工作。"

這個時候不是真正的OOM,而是提前丟擲異常,防止OOM發生。

舉例:Session

Tomcat的session問題。程式中將一個session儲存的一個全域性的ConcurrentHashMap。由於外部有大量的HttpClient訪問建立的臨時session,這些httpclient程式在訪問時並沒有儲存sessionKey和cookie,導致每次請求都以為以前沒有建立過session,都會重新建立session。session的建立時發生在request.getSession()方法呼叫時,這些操作在一些框架中也會隱藏建立,雖然這些session很小,但是積少成多,積土成山。

OOM的後果

OOM不一定會宕機。大多OOM都是靜態變數指向了一個只增不減的物件,比如一個HashMap。但有時候OOM不一定是這麼來的。

如果一個區域性變數引發的OOM,可以try catch住,而不是拋到容器頂層來處理,這樣可以讓程序繼續存在。當丟擲的錯誤在方法外捕獲的時候,區域性變數的作用域其實已經脫離,如果發生一次FullGC,記憶體可以釋放掉,系統可以照常執行,比如:

public static void testOOM() {
try {
List<String> list = Lists.newArrayList();
while (true) {
list.add("hello");
}
catch (Throwable e) {
System.out.println("message:" + e.getMessage());
}
}

但是這樣的話,不容易發現問題,只有這段程式碼引發的錯誤被大家知道,才會去跟進,不如OOM宕機能夠更加直接的讓人認識到問題的嚴重性。

而且,這種情況的OOM,用jmap將記憶體dump出來的時候,或者記憶體溢位時dump出來看到的記憶體情況可能很正常,因為在dump的時候會先做一次FullGC,這個時候局域變數脫離了作用域,就沒辦法捕獲它了。

PermGen OOM

永久代存放的東西有Class和一些常量。例如:

public static void testGenOOM() {
List<String> list = Lists.newArrayList();
int i = 0;
while (true) {
list.add(("hello1234567890-qwertyuioasdfghjkl;zxcvbnm,[email protected]#$%^&*()" + (i++)).intern());
}
}

這樣就會導致Perm的OOM,OutOfMemoryError,PermGen space。

如果把List去掉,只是不停的intern(),則不會OOM。但是並不是說這樣就沒有問題了,因為這時候系統在不停的做FullGC(用-XX:PrintGCDetail就可以看到)。FullGC會回收常量池中的內容,這也是FullGC的原因之一。

JDK1.7的String常量已經不放在PermGen區了。

Class的載入也可能導致PermGen的OOM。注意這裡Class並沒有被解除安裝,而是導致記憶體溢位。

因為Class的解除安裝條件非常苛刻,只有這個Class所對應的的ClassLoader下的所有Class(包括其它類的Class)都沒有活著的物件引用,才會被解除安裝。

如果每次CGlib動態建立時,都重新給它設定一個ClassLoader,這個ClassLoader只加載這個類的話,且載入之後這個類很快就沒有引用了,這樣就不會OOM了。

所以:如果想要動態載入一些類,或者動態編譯一段java程式碼,最好有一個單獨的ClassLoader,這樣如果Class發生變化或者被替換,原先的Class就可以被當做垃圾釋放了。

DirectBuffer OOM

java的普通IO採用輸入輸出流來實現。

輸入流:終端->直接記憶體->JVM。輸出流:JVM->直接記憶體->終端。

這期間有多次Kernel與JVM之間的記憶體拷貝。有時為了提高速度,會想辦法利用直接記憶體

Java中有一塊區域叫做DirectBuffer,它不是Java Heap的一部分,而是C Heap的一部分。它有大小限制,在FullGC的時候會回收,該區域使用不當會有“大坑”。

StackOverFlow Error

程式執行過程中,方法分配時會分配棧幀(Frame)來存放本地變數,後進先出棧,PC暫存器等資訊,如果方法巢狀或者遞迴呼叫,在不斷分派過程中會佔用十分多的空間。

Java為了控制執行緒棧無休止的增長(防止死遞迴的出現),就會設定一個私有棧空間大小,量級大概是256KB~1MB。執行緒私有棧所佔用的不再是堆記憶體,通常叫做Native Memory。若使用空間超過限制,就會出現StackOverFlowError。

如果需要使用遞迴解決問題,一定要設定好遞迴呼叫層數,或者設定好遞迴結束的條件。

死遞迴與死迴圈不同。死迴圈是類似於while(true),不會造成棧空間的遞增。而死遞迴需要記錄退回的路徑,就必須記住遞推過程中的方法呼叫過程,以及每個方法執行過程中的本地變數,我們稱之為上下文資訊。

隨著內容增加,就會佔用很多記憶體空間,防止其無限制增長,做了安全處理。

舉例:

子類呼叫父類方法(為了複用),父類再直接或者間接呼叫子類方法(多型),因為某種因素形成了一個環,變成了死遞迴。

定位StackOverFlow很容易,執行緒棧資訊明確給出了呼叫路徑。

其他記憶體溢位現象

unnable to create new native thread

棧本身是佔用空間的,只是它佔用的是native memory。每個私有棧空間都有大小限制,每一個執行緒對應它自己的棧空間。如果反覆申請了大量執行緒並讓它們處於執行狀態,那麼這些執行緒所佔用的native memory空間就會很多。

如果實體記憶體不夠,或者作業系統限制了單個程序使用的最大記憶體,那麼當有“大量”執行緒分配的時候,可能會丟擲異常:unnable to create new native thread。它表示無法分配本地記憶體。

request {} byte for {} out of swap

地址空間開始不夠用了。不一定是實體地址,還有swap,顯示卡,網絡卡等等。

IOException:too many open files

有太多沒有關閉的檔案或者套接字

還有一些其他的比如JNI呼叫問題,Swap與記憶體頻繁互動導致系統假死,JVM核心bug導致程序crash掉(此時還需要看crash日誌)。

遇到OOM時,可以設定HeapDumpOnOutOfMemoryError引數,設定引數之後,JVM會在OOM時dump出一份記憶體的二進位制檔案,該檔案可以用MAT工具來分析。

相關推薦

java特種兵讀書筆記3-5——java程式設計師OSOOM

HeapSize OOM public static void main(String[] args) { List<String> list = Lists.newArrayList(); while (true) { list.add("hello");

《深入理解Java虛擬機:JVM高級屬性與最佳實踐》讀書筆記更新中

pen 內存區域 深度 span 進化 ria 最短 描述 core 第一章:走進Java 概述 Java技術體系 Java發展史 Java虛擬機發展史 1996年 JDK1.0,出現Sun Classic VM HotSpot VM, 它是 Sun JDK 和 Open

tcp/ip 卷一 讀書筆記3為什麽既要有IP地址又要有MAC地址

維護 移動 理論 集線器 協議 合並 所有 變更 影響 網絡層 首先明確一點,並不是所有的網絡之間傳輸數據都需要mac地址和ip地址,比如說點對點線路之間的通信就沒有MAC地址,網絡層使用ipx協議時就沒有ip地址,但是在當前的主流網絡中,我們都使用ip地址和mac地址 既

Java暑期學習筆記3

ring out 顯示 字節數 順序 作用 提示 string轉換 gbk # 2018.7.11 # * 1.匿名內部類(只針對重寫一個方法時候使用,不能向下轉型,因為沒有子類類名) * new Inter(){ public

老男孩shell實戰讀書筆記1-5章節

老男孩shell教程(1-5章節) 關於檢視系統變數命令 set: 輸出所有的變數,包括全域性變數和區域性變數 env:只顯示全域性變數 declare:輸出所有的變數、函式、整數和已經匯出(export)的變數 刪除環境變數 unset 變數名 關於設定(全域性)環境變數的三種方法 expo

程式碼大全 讀書筆記3軟體構建中的設計

1. 選擇程式語言 熟悉的語言 高階的語言 更能表達程式設計中各種概念的語言 每種語言都有自己的優點和弱點,要知道所選用語言的明確優點和弱點。 問問自己,採用的程式設計實踐是對你所用的程式語言的正確響應,還是受它的控制,記得“深入一種語言去程式設計”,不要僅“在一種語言上程式設計

kafka 權威指南--讀書筆記-3向kafka寫入資料

(1)kafka生產者設計和元件 (1)不同的應用場景對訊息有不同的需求,即是否允許訊息丟失、重複、延遲以及吞吐量的要求。不同場景對Kafka生產者的API使用和配置會有直接的影響。 例子1:信用卡事務處理系統,不允許訊息的重複和丟失,延遲最大500ms,對吞吐量要求較高

認識django2.0讀書筆記3---第三章 檢視和URL配置

文件下載地址:Django_2.0_中文教程  http://download.csdn.net/detail/julius_lee/6620099 線上地址:http://djangobook.py3k.cn/2.0/ Django 2.0 Book 關鍵內容記錄,主要

《Spring In Action》 讀書筆記3 -- factory-method的應用

因為單例類的特殊性,spring對於單例類的注入提供了factory-method屬性,先上程式碼。 OneInstance類: package spring.ioc02; public class OneInstance { public

《瘋狂Java程式設計師基本修養》筆記1-5

第一章: 1. 陣列初始化的兩種方式: 靜態初始化:初始化時由陣列顯式指定每個陣列元素的初始值,由系統決定陣列的長度。 動態初始化:初始化時程式設計師只指定陣列長度,由系統為陣列元素分配初始值。

Object-C高階程式設計讀書筆記3——Block的變數擷取

之前我們對於Block的定義為 “帶有自動變數值的匿名函式”。通過前面的介紹,知道了Block能夠保持傳入其中的變數的值,即使在Block外部這些傳入的值已經結束了其作用域,但是在Block被呼叫時,

《JavaScript 模式》讀書筆記3— 字面量和建構函式1

  新的篇章開始了,本章開始,所有的內容都是十分有價值和意義的。本章主要的內容包括物件字面量、建構函式、陣列字面量、正則字面量、基本值型別字面量以及JSON等。在大家的工作和實際應用中也有一定的指導意義。   一、物件字面量   我們直接來先看一下程式碼: // 開始時定義一個空物件 var do

《TCP/IP具體解釋》讀書筆記21章-TCP的超時與重傳

打開 定時器 是否 檢查 例如 技術 blog 信息 全部 TCP提供可靠的運輸層。它使用的方法之中的一個就是確認從還有一端收到的數據。但數據和確認都有可能會丟失。TCP通過在發送時設置一個定時器來解決這樣的問題。假設當定時器溢出時還沒有收到確認,它就重傳該數據。對於實現

《大數據日知錄:架構與算法》讀書筆記多圖

打通 導論 ges wid 技術分享 二次 思維 知識點 很好 第二次讀這本書,這次是精讀,畫了思維導圖。書很好,完整的知識結構和由淺入深的介紹,非常全面以至於知識點都梳理了三天。 作為導論式的總覽,對大數據領域有了個總體的認識,接下來可以更針對性地加強和實踐。 總體上

《TCP/IP具體解釋》讀書筆記19章-TCP的交互數據流

font alt 算法 方向 它的 字節 隨機 收集 計算 在TCP進行傳輸數據時。能夠分為成塊數據流和交互數據流兩種。假設按字節計算。成塊數據與交互數據的比例約為90%和10%,TCP須要同一時候處理這兩類數據,且處理的算法不同。書籍本章中以Rlogin應用為例觀察交

《現代前端技術解析》第一章讀書筆記未完成

服務 異步 網絡請求 會話 開始 註冊 復雜 技術 顯示   今天是2017年6月26日,星期一,開始從第一章看起。第一章主要講的是前端技術的發展概況以及一些必須掌握的瀏覽器基礎知識與常用開發技術。   頁面內容多而復雜,為了保證開發效率,我們可以借助符合特定場景的前端框架

《Android源代碼設計模式解析與實戰》讀書筆記二十

apt 通過 rip idv ber list adaptee 無法 技術分享 第二十章、適配器模式 適配器模式是結構型設計模式之中的一個,它在我們的開發中使用率極高,比方ListView、GridView以及RecyclerView都須要使用A

《Linux內核設計與實現》讀書筆記十二- 內存管理

enable vmalloc 緩沖 turn lean png border 編譯 不一致 內核的內存使用不像用戶空間那樣隨意,內核的內存出現錯誤時也只有靠自己來解決(用戶空間的內存錯誤可以拋給內核來解決)。 所有內核的內存管理必須要簡潔而且高效。 主要內容: 內

《Linux內核設計與實現》讀書筆記十六- 頁高速緩存和頁回寫

第一次 源碼 進行 lose 減少 文件緩存 掩碼 recycle 創建 主要內容: 緩存簡介 頁高速緩存 頁回寫 1. 緩存簡介 在編程中,緩存是很常見也很有效的一種提高程序性能的機制。 linux內核也不例外,為了提高I/O性能,也引入了緩存機

Python編程入門到實踐 - 筆記 3

python 練習 練習內容包括創建並訪問列表列表的索引使用列表中的各個值修改列表中的元素在列表中添加元素 append()在列表中插入元素 insert()在列表中刪除元素 del,pop()根據值刪除列表中的元素 remove()對列表中的元素進行排列 1)永久性修改 sort(),按字母表正向