線上日誌反饋內存溢出問題。根據用戶反饋,客戶操做一段時間以後,APP 內存溢出崩潰。html
(1) 分析線上日誌,發現主要分兩種:java
第一種以下,多是某個死循環致使內存不停增大致使:web
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available數據庫
第二種以下,壓死駱駝的最後一根稻草:性能優化
java.lang.OutOfMemoryError: Failed to allocate a 16 byte allocation with 0 free bytes and 3GB until OOM, max allowed footprint 536872136, growth limit 536870912服務器
這兩種狀況下都沒法定位問題所在。app
經過觀察發生的版本信息,是從V9.5.6開始的,因而就開始查那期改動的功能(主要是webview優化,其餘的是SDK更新),因爲沒有線上出問題的機型,就找個相似的華爲機器操做webview,發現內存增加並不快,操做很長時間內存變化不大。jvm
(2) 分析用戶操做路徑ide
讓工程導出出現OOM問題用戶最近的操做記錄,埋點,頁面停留,崩潰日誌,觀察進入了哪些頁面,可是照着操做路徑操做,也沒發現崩潰。工具
(3) 統計出現問題的機型,前五都是華爲的,系統版本7.0以上,內存3GB以上。
(4) 使用LeakCanary + Android Profiler排查泄漏的地方,查看線上日誌的時候,偶然發現測試有一部手機出現了問題,華爲P8,而後使用工具根據用戶操做路徑查各個模塊泄漏問題,發現委託登陸有個持續的10M泄漏,比較大的泄漏還有開屏廣告頁和行情登陸頁面34M。這個手機給APP分配的可用內存最大爲256M,操做幾回登陸頁面就崩潰了。
開屏頁:
private ScheduledFuture<?> mTaskFuture = null;
private void gotoMain() {
...
AdCountDownTask adCountDownTask = new AdCountDownTask();
HexinThreadPool.cancelTaskFutre(mTaskFuture, true);
mTaskFuture = HexinThreadPool.getThreadPool().sheduleWithFixedDelay(xxx);
handler.sendEmptyMessageDelayed(xxx, xxx);
}
protected void onDestory() {
...
if (mTaskFuture != null) {
HexinThreadPool.cancelTaskFutre(mTaskFuture, true);
mTaskFuture = null;
}
}
複製代碼
委託登陸
public class AdsCT {
private Runnable runable = new Runnable {
// 輪播圖片
handler.postDelay(runnable, time);
}
public void onRemove() {
handler.removeCallbacksAndMessages(null);
}
}
public class WeituoLogin {
public void onRemove() {
adsCt.onRemove();
}
}
複製代碼
(5) 修改查出來比較大的內存泄漏,券商給以前出問題的兩個用戶單獨安裝,發現仍是存在,券商反饋有個用戶安裝9.4.5的一直都沒問題,升級到最新以後就出現問題了,如今懷疑仍是9.4.6哪一個模塊致使的。
全稱「Out Of Memory」,翻譯成中文就是「內存用完了」,來源於java.lang.OutOfMemoryError。
方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆);棧又分爲java虛擬機棧和本地方法棧主要用於方法的執行。
內存泄露:申請使用完的內存沒有釋放,致使虛擬機不能再次使用該內存,此時這段內存就泄露了,由於申請者不用了,而又不能被虛擬機分配給別人用。
內存溢出:申請的內存超出了JVM能提供的內存大小,此時稱之爲溢出。
可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。不可達對象。
申請一塊內存時,若是當前可用內存不足,則會出發一次GC,而後再次申請,此時若是內存還不足,則會拋出OOM。
// 錯誤示例,內部類持有外部對象致使泄漏
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
// 修改後,在activity onDestory()的時候,調用myHandler.removeCallbacksAndMessages(null);
private static class MyHandler extends Handler {
private WeakReference<MainActivity> mainActivityWeakReference;
public MyHandler(MainActivity mainActivity) {
mainActivityWeakReference = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mainActivityWeakReference.get();
if (mainActivity == null) {
return;
}
mainActivity.jump(null);
super.handleMessage(msg);
}
}
// context被線程持有
private void registerException(final Context context) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
String appName = context.getResources().getString(R.string.app_name);
}
};
}
複製代碼
strong soft weak phantom(其實還有一種FinalReference,這個由jvm本身使用,外部沒法調用到),主要的區別體如今gc上的處理,以下:
Strong類型,也就是正常使用的類型,不須要顯示定義,只要沒有任何引用就能夠回收
SoftReference類型,若是一個對象只剩下一個soft引用,在jvm內存不足的時候會將這個對象進行回收
WeakReference類型,若是對象只剩下一個weak引用,那gc的時候就會回收。和SoftReference均可以用來實現cache
PhantomReference類型,
private void testStream() {
InputStream in = null;
try {
in = new FileInputStream("xxx.xml");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
}
}
複製代碼
單例持有Context,監聽器等
Activity的監聽,是在Activity onDestory()執行以後再看是否被回收,在Application建立的時候,註冊了監聽
Application.ActivityLifecycleCallbacks lifecycleCallbacks = new..{
...
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
}
複製代碼
工程中查看Page是否被釋放,能夠在onRemove()的時候使用
RefWatcher watch(Object obj) 複製代碼
使用的WeakReference+ReferenceQueue實現
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
複製代碼
若是軟引用或弱引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用或弱引用加入到與之關聯的引用隊列中,由此可判斷對象是否被回收。
@Override public void runGc() {
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization(); //強制調用已經失去引用的對象的finalize方法,確保釋放實例佔用的所有資源。
}
private void enqueueReferences() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
複製代碼
(1) 對內存文件進行分析,因爲這個過程比較耗時,所以最終會把這個工做交給運行在另一個進程中的HeapAnalyzerService來執行。
(2) 利用HAHA將以前dump出來的內存文件解析成Snapshot對象,解析獲得的Snapshot對象直觀上和咱們使用MAT進行內存分析時候羅列出內存中各個對象的結構很類似,它經過對象之間的引用鏈關係構成了一棵樹,咱們能夠在這個樹種查詢到各個對象的信息,包括它的Class對象信息、內存地址、持有的引用及被持有的引用關係等。
(3) 爲了可以準確找到被泄漏對象,LeakCanary經過被泄漏對象的弱引用來在Snapshot中定位它。由於,若是一個對象被泄漏,必定也能夠在內存中找到這個對象的弱引用,再經過弱引用對象的reference就能夠直接定位被泄漏對象。
(4) 在Snapshot中找到一條有效的到被泄漏對象之間的引用路徑,從被泄露的對象開始,採用的方法是相似於廣度優先的搜索策略,將持有它引用的節點(父節點),加入到一個FIFO隊列中,一層一層向上尋找,哪條路徑最早到達GCRoot就表示它應該是一條最短路徑。
(5) 將以前查找的最短路徑轉換成最後須要顯示的LeakTrace對象,這個對象中包括了一個由路徑上各個節點LeakTraceElement組成的鏈表,表明了檢查到的最短泄漏路徑。最後一個步驟就是將這些結果封裝成AnalysisResult對象而後交給DisplayLeakService進行處理。這個service主要的工做是將檢查結果寫入文件,以便以後可以直接看到最近幾回內存泄露的分析結果,同時以notification的方式通知用戶檢測到了一次內存泄漏。使用者還能夠繼承這個service類來並實現afterDefaultHandling來自定義對檢查結果的處理,好比將結果上傳剛到服務器等。
某些狀況下是能夠的,好比說局部申請一個很大的內存,若是形成了OOM,在catch的地方釋放掉,程序仍是能夠繼續往下執行的,可是若是捕獲以後釋放不掉,也仍是會崩潰。
編譯器會默認爲成員內部類添加了一個指向外部類對象的引用,那麼這個引用是如何賦初值的呢?
public com.xxx.Outter$Inner(com.cxh.test2.Outter);
複製代碼
咱們在定義的內部類的構造器是無參構造器,編譯器仍是會默認添加一個參數,該參數的類型爲指向外部類對象的一個引用,因此成員內部類中的Outter this&0 指針便指向了外部類對象,所以能夠在成員內部類中隨意訪問外部類的成員。從這裏也間接說明了成員內部類是依賴於外部類的,若是沒有建立外部類的對象,則沒法對Outter this&0引用進行初始化賦值,也就沒法建立成員內部類的對象了。
外部類和內部類編譯後會生成兩個.class文件,若是局部變量的值在編譯期間就能夠肯定,則直接在匿名內部裏面建立一個拷貝。若是局部變量的值沒法在編譯期間肯定,則經過構造器傳參的方式來對拷貝進行初始化賦值。 拷貝的話,若是一個值改變了,就會形成數據不一致性,爲了解決這個問題,java編譯器就限定必須將變量a限制爲final變量,不容許對變量a進行更改(對於引用類型的變量,是不容許指向新的對象),這樣數據不一致性的問題就得以解決了。
private static StaticObj staticObj = new StaticObj();
static class StaticObj {
@Override
protected void finalize() throws Throwable {
staticObj = new StaticObj();
super.finalize();
}
}
複製代碼
// System.gc()
/** * If we just ran finalization, we might want to do a GC to free the finalized objects. * This lets us do gc/runFinlization/gc sequences but prevents back to back System.gc(). */
private static boolean justRanFinalization;
public static void gc() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = justRanFinalization;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
public static void runFinalization() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = runGC;
runGC = false;
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
Runtime.getRuntime().runFinalization();
synchronized (LOCK) {
justRanFinalization = true;
}
}
複製代碼
參考: