關於是否可以try catch OutOfMemoryError的討論
關於是否可以try catch OutOfMemoryError的討論
目錄
[TOC]
問題由來
這是一家公司的面試題目,感覺有點意思,所以面試回來準備測試下什麼情況
問題論點
對於這個問題,主要討論兩種OutOfMemory可能性,一種是突然使用了大量記憶體,比如載入了特別巨大的圖片,第二是記憶體洩漏.
然後還有個問題是,一旦發生OOM,引發OOM的操作是否會成功,如果會成功賦值是否會成功呢?理論上操作和賦值都不會成功的,但是我覺得有必要嘗試一下.
構建測試程式碼
那麼針對問題構建測試程式碼如下
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/out" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:text="變數在try外面" android:textSize="15sp" /> <Button android:id="@+id/in" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:text="變數在try裡面" android:textSize="15sp" /> <Button android:id="@+id/add" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:text="新增陣列" android:textSize="15sp" /> <Button android:id="@+id/action" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:text="其他正常操作" android:textSize="15sp" /> <Button android:id="@+id/gc" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:text="垃圾回收" android:textSize="15sp" /> <TextView android:id="@+id/left" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:padding="10dp" android:textSize="15sp" /> </LinearLayout>
MainActivity.java
package com.yxf.trytocatchoutofmemory; import android.app.ActivityManager; import android.content.Context; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.util.ArrayList; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private ArrayList<byte[]> mArrayList = new ArrayList<byte[]>(); private Button mOutButton, mInButton, mAddButton, mActionButton, mGcButton; private TextView mLeftMemoryView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mOutButton = findViewById(R.id.out); mInButton = findViewById(R.id.in); mAddButton = findViewById(R.id.add); mActionButton = findViewById(R.id.action); mGcButton = findViewById(R.id.gc); mLeftMemoryView = findViewById(R.id.left); mOutButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { byte[] values = null; try { values = new byte[1024 * 1024 * 1024]; } catch (OutOfMemoryError error) { error.printStackTrace(); } if (values == null) { Log.d(TAG, "onClick: values is null"); } else { Log.d(TAG, "onClick: values not null"); } updateLeftMemoryView(); } }); mInButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { byte[] values = new byte[1024 * 1024 * 1024]; } catch (OutOfMemoryError error) { error.printStackTrace(); } updateLeftMemoryView(); } }); mAddButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { mArrayList.add(new byte[1024 * 1024 * 10]); } catch (OutOfMemoryError error) { error.printStackTrace(); } updateLeftMemoryView(); } }); mActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { byte[] bytes = new byte[1024 * 1024 * 10]; updateLeftMemoryView(); } }); mGcButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { System.gc(); updateLeftMemoryView(); } }); updateLeftMemoryView(); } private void updateLeftMemoryView() { String s = String.format("記憶體總計 : %#.3f , 剩餘 : %#.3f", (Runtime.getRuntime().totalMemory() * 1.0 / 1024 / 1024), (Runtime.getRuntime().freeMemory() * 1.0 / 1024 / 1024)); mLeftMemoryView.setText(s); } }
測試介面

image
測試情況
變數在try外面
log如下
D: JIT code cache reset in 0 ms (0 bytes 1/0) GC_FOR_ALLOC freed 254K, 10% free 3058K/3376K, paused 10ms, total 12ms I: Forcing collection of SoftReferences for 1073741836-byte allocation D: GC_BEFORE_OOM freed 2K, 10% free 3056K/3376K, paused 9ms, total 12ms E: Out of memory on a 1073741836-byte allocation. I: "main" prio=5 tid=1 RUNNABLE | group="main" sCount=0 dsCount=0 obj=0x94c64bd8 self=0xb90c6500 | sysTid=1571 nice=0 sched=0/0 cgrp=[fopen-error:2] handle=-1216952576 | state=R schedstat=( 0 0 0 ) utm=45 stm=23 core=0 at com.yxf.trytocatchoutofmemory.MainActivity$1.onClick(MainActivity.java:~37) at android.view.View.performClick(View.java:4438) at android.view.View$PerformClick.run(View.java:18422) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5019) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) W: java.lang.OutOfMemoryError at com.yxf.trytocatchoutofmemory.MainActivity$1.onClick(MainActivity.java:37) at android.view.View.performClick(View.java:4438) at android.view.View$PerformClick.run(View.java:18422) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5019) at java.lang.reflect.Method.invokeNative(Native Method) W:at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) D: onClick: values is null
實際上這個OutOfMemoryError成功被catch到了,而且程式並沒有崩潰.
程式中的剩餘記憶體等也沒有發生太大變化,這說明OutOfMemoryError觸發,其實並不會真正分配記憶體.
而且從上面的日誌資訊上也可以瞭解到一些資訊
比如:
- 在分配可能會導致OutOfMemoryError的記憶體前,會先做一次垃圾回收
- 在做這次垃圾回收時會把SoftReference也回收掉
- 變數並沒有被賦值成功
變數在Try裡面
這個測試是為了測試是否會成功建立並且賦值的,由於上面結論已經確定不行了,所以這個測試其實失去了意義.
新增陣列測試
這個測試是模擬記憶體洩漏的,類比反覆的啟動一個被其他類引用的activity.
這裡是一直給一個ArrayList新增資料,直到接近OutOfMemory.
結果如圖

image
從圖中可知這個也可以catch到OutOfMemoryError
而且還可以瞭解到其他資訊:
- 應用分配的總記憶體並不是固定的,而是會根據使用情況增長的,而且這個總記憶體是會根據手機ram改變的,模擬器2g記憶體最大180mb,我真機6g記憶體,最大380mb.
說個題外話,那麼我們手機記憶體明明都那麼大了,卻依然還是卡的原因出來了!!!無良App就是,老子最重要,老子效能,體驗都要最好,然後無良App們就通過什麼LruCache各種使用強引用快取,佔著記憶體不放,然後它真的實現了,記憶體都給它霸佔著,系統是卡,其他後面開的App也卡,但是它就是不卡,23333333.
其他正常操作測試
這個測試是用來證明catch記憶體洩漏導致的oom是否有意義的
測試結果如下

image
這個操作只是一個正常而又簡單的分配一個byte陣列的操作,但是程式崩了.
由於之前的記憶體洩漏已經導致記憶體幾乎使用完了,catch OutOfMemoryError雖然成功,但是並沒有意義,因為任何一個接下來的操作都可能依然會導致OutOfMemoryError的出現.
總結
OutOfMemoryError不應該去catch,出現OutOfMemoryError不管是因為一次巨大的記憶體分配還是記憶體洩漏導致,都是程式設計的問題,如果是大記憶體操作,應該想辦法一點點載入,或者壓縮資源來載入,如果是巨大數量的排序問題,則可以選擇外排序的方式進行,如果是記憶體洩漏,則需要尋找程式自身的設計問題.
測試原始碼
ofollow,noindex">TryToCatchOutOfMemory