摘要:不能滿足被回收的條件,盡管調(diào)用也還是不能得到回收這就造成了內(nèi)存泄漏。種解決單例中的內(nèi)存泄漏將引用置為銷毀監(jiān)聽使用弱引用將監(jiān)聽器放入弱引用中從弱引用中取出回調(diào)通過(guò)第七小點(diǎn)就能完美的解決單例中回調(diào)引起的內(nèi)存泄漏。
我們?yōu)槭裁匆獌?yōu)化內(nèi)存
在 Android 中我們寫的 .java 文件,最終會(huì)編譯成 .class 文件, class 又由類裝載器加載后,在 JVM 中會(huì)形成一份描述 class 結(jié)構(gòu)的元信息對(duì)象,通過(guò)該元信息對(duì)象可以知道 class 的結(jié)構(gòu)信息 (構(gòu)造函數(shù)、屬性、方法)等。JVM 會(huì)把描述類的數(shù)據(jù)從 class 文件加載到內(nèi)存,Java 有一個(gè)很好的管理內(nèi)存的機(jī)制,垃圾回收機(jī)制 GC 。為什么 Java 都給我們提供了垃圾回收機(jī)制,程序有時(shí)還會(huì)導(dǎo)致內(nèi)存泄漏,內(nèi)存溢出 OOM,甚至導(dǎo)致程序 Crash 。接下來(lái)我們就對(duì)實(shí)際開發(fā)中出現(xiàn)的這些內(nèi)存問(wèn)題,來(lái)進(jìn)行優(yōu)化。
JAVA 虛擬機(jī)我們先來(lái)大概了解一下 Java 虛擬機(jī)里面運(yùn)行時(shí)的數(shù)據(jù)區(qū)域有哪些,如果想深入了解 Java 虛擬機(jī) 建議可以購(gòu)買<<深入理解 Java 虛擬機(jī)>> 或者直接點(diǎn)擊我這里的 PDF 版本 密碼: jmnf
線程獨(dú)占區(qū)
程序計(jì)數(shù)器
相當(dāng)于一個(gè)執(zhí)行代碼的指示器,用來(lái)確認(rèn)下一行執(zhí)行的地址
每個(gè)線程都有一個(gè)
沒有 OOM 的區(qū)
虛擬機(jī)棧
我們平時(shí)說(shuō)的棧就是這塊區(qū)域
java 虛擬機(jī)規(guī)范中定義了 OutOfMemeory , stackoverflow 異常
本地方法棧
java 虛擬機(jī)規(guī)范中定義了 OutOfMemory ,stackoverflow 異常
注意
在 hotspotVM 中把虛擬機(jī)棧和本地方法棧合為了一個(gè)棧區(qū)
線程共享區(qū)方法區(qū)
ClassLoader 加載類信息
常量、靜態(tài)變量
編譯后的代碼
會(huì)出現(xiàn) OOM
運(yùn)行時(shí)常量池
public static final
符號(hào)引用類、接口全名、方法名
java 堆 (本次需要優(yōu)化的地方)
虛擬機(jī)能管理的最大的一塊內(nèi)存 GC 主戰(zhàn)場(chǎng)
會(huì)出現(xiàn) OOM
對(duì)象實(shí)例
數(shù)據(jù)的內(nèi)容
JAVA GC 如何確定內(nèi)存回收隨著程序的運(yùn)行,內(nèi)存中的實(shí)例對(duì)象、變量等占據(jù)的內(nèi)存越來(lái)越多,如果不及時(shí)進(jìn)行回收,會(huì)降低程序運(yùn)行效率,甚至引發(fā)系統(tǒng)異常。
目前虛擬機(jī)基本都是采用可達(dá)性分析算法,為什么不采用引用計(jì)數(shù)算法呢?下面就說(shuō)說(shuō)引用計(jì)數(shù)法是如果統(tǒng)計(jì)所有對(duì)象的引用計(jì)數(shù)的,再對(duì)比可達(dá)性分析算法是如何解決引用計(jì)數(shù)算法的不足。下面就來(lái)看下這 2 個(gè)算法:
引用計(jì)數(shù)算法每個(gè)對(duì)象有一個(gè)引用計(jì)數(shù)器,當(dāng)對(duì)象被引用一次則計(jì)數(shù)器加一,當(dāng)對(duì)象引用一次失效一次則計(jì)數(shù)器減一,對(duì)于計(jì)數(shù)器為 0 的時(shí)候就意味著是垃圾了,可以被 GC 回收。
下面通過(guò)一段代碼來(lái)實(shí)際看下
public class GCTest {
private Object instace = null;
public static void onGCtest() {
//step 1
GCTest gcTest1 = new GCTest();
//step 2
GCTest gcTest2 = new GCTest();
//step 3
gcTest1.instace = gcTest2;
//step 4
gcTest2.instace = gcTest1;
//step 5
gcTest1 = null;
//step 6
gcTest2 = null;
}
public static void main(String[] arg) {
onGCtest();
}
}
分析代碼
//step 1 gcTest1 引用 + 1 = 1
//step 2 gcTest2 引用 + 1 = 1
//step 3 gcTest1 引用 + 1 = 2
//step 4 gcTest2 引用 + 1 = 2
//step 5 gcTest1 引用 - 1 = 1
//step 6 gcTest2 引用 - 1 = 1
很明顯現(xiàn)在 2 個(gè)對(duì)象都不能用了都為 null 了,但是 GC 確不能回收它們,因?yàn)樗鼈儽旧淼囊糜?jì)數(shù)不為 0 。不能滿足被回收的條件,盡管調(diào)用 System.gc() 也還是不能得到回收, 這就造成了 內(nèi)存泄漏 。當(dāng)然,現(xiàn)在虛擬機(jī)基本上都不采用此方式。
可達(dá)性分析算法
從 GC Roots 作為起點(diǎn)開始搜索,那么整個(gè)連通圖中額對(duì)象邊都是活對(duì)象,對(duì)于 GC Roots 無(wú)法到達(dá)的對(duì)象便成了垃圾回收的對(duì)象,隨時(shí)可能被 GC 回收。
可以作為 GC Roots 的對(duì)象
虛擬機(jī)棧正在運(yùn)行使用的引用
靜態(tài)屬性 常量
JNI 引用的對(duì)象
GC 是需要 2 次掃描才回收對(duì)象,所以我們可以使用 finalize 去救活丟失的引用
@Override
protected void finalize() throws Throwable {
super.finalize();
instace = this;
}
到了這里,相信大家已經(jīng)能夠弄明白這 2 個(gè)算法的區(qū)別了吧?反正對(duì)于對(duì)象之間循環(huán)引用的情況,引用計(jì)數(shù)算法無(wú)法回收這 2 個(gè)對(duì)象,而可達(dá)性是從 GC Roots 開始搜索,所以能夠正確的回收。
不同引用類型的回收狀態(tài)Object strongReference = new Object()
如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它,當(dāng)內(nèi)存空間不足, Java 虛擬機(jī)寧愿拋出 OOM 錯(cuò)誤,使程序異常 Crash ,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題.如果強(qiáng)引用對(duì)象不再使用時(shí),需要弱化從而使 GC 能夠回收,需要:
strongReference = null; //等 GC 來(lái)回收
還有一種情況,如果:
public void onStrongReference(){
Object strongReference = new Object()
}
在 onStrongReference() 內(nèi)部有一個(gè)強(qiáng)引用,這個(gè)引用保存在 java 棧 中,而真正的引用內(nèi)容 (Object)保存在 java 堆中。當(dāng)這個(gè)方法運(yùn)行完成后,就會(huì)退出方法棧,則引用對(duì)象的引用數(shù)為 0 ,這個(gè)對(duì)象會(huì)被回收。
但是如果 mStrongReference 引用是全局時(shí),就需要在不用這個(gè)對(duì)象時(shí)賦值為 null ,因?yàn)?強(qiáng)引用 不會(huì)被 GC 回收。
如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存,只要垃圾回收器沒有回收它,該對(duì)象就可以被程序使用。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收, java 虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
注意: 軟引用對(duì)象是在 jvm 內(nèi)存不夠的時(shí)候才會(huì)被回收,我們調(diào)用 System.gc() 方法只是起通知作用, JVM 什么時(shí)候掃描回收對(duì)象是 JVM 自己的狀態(tài)決定的。就算掃描到了 str 這個(gè)對(duì)象也不會(huì)回收,只有內(nèi)存不足才會(huì)回收。
弱引用與軟引用的區(qū)別在于: 只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過(guò)由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。
弱引用可以和一個(gè)引用隊(duì)列聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java 虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
可見 weakReference 對(duì)象的生命周期基本由 GC 決定,一旦 GC 線程發(fā)現(xiàn)了弱引用就標(biāo)記下來(lái),第二次掃描到就直接回收了。
注意這里的 referenceQueuee 是裝的被回收的對(duì)象。
@Test
public void onPhantomReference()throws InterruptedException{
String str = new String("123456");
ReferenceQueue queue = new ReferenceQueue();
// 創(chuàng)建虛引用,要求必須與一個(gè)引用隊(duì)列關(guān)聯(lián)
PhantomReference pr = new PhantomReference(str, queue);
System.out.println("PhantomReference:" + pr.get());
System.out.printf("ReferenceQueue:" + queue.poll());
}
虛引用顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。
虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于: 虛引用必須和引用隊(duì)列 (ReferenceQueue) 聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
引用類型 | 調(diào)用方式 | GC | 是否內(nèi)存泄漏 |
---|---|---|---|
強(qiáng)引用 | 直接調(diào)用 | 不回收 | 是 |
軟引用 | .get() | 視內(nèi)存情況回收 | 否 |
弱引用 | .get() | 回收 | 不可能 |
虛引用 | null | 任何時(shí)候都可能被回收,相當(dāng)于沒有引用一樣 | 否 |
工具很多,掌握原理方法,工具隨意挑選使用。
top/procrank meinfo Procstats DDMS MAT Finder - Activity LeakCanary LeakInspector 內(nèi)存泄漏產(chǎn)生的原因: 一個(gè)長(zhǎng)生命周期的對(duì)象持有一個(gè)短生命周期對(duì)象的引用,通俗點(diǎn)講就是該回收的對(duì)象,因?yàn)橐脝?wèn)題沒有被回收,最終會(huì)產(chǎn)生 OOM。
下面我們來(lái)利用 Profile 來(lái)檢查項(xiàng)目是否有內(nèi)存泄漏
怎么利用 profile 來(lái)查看項(xiàng)目中是否有內(nèi)存泄漏
在 AS 中項(xiàng)目以 profile 運(yùn)行
在 MEMORY 界面中選擇要分析的一段內(nèi)存,右鍵 export
Allocations: 動(dòng)態(tài)分配對(duì)象個(gè)數(shù)
Deallocation: 解除分配的對(duì)象個(gè)數(shù)
Total count: 對(duì)象的總數(shù)
Shalow Size: 對(duì)象本身占用的內(nèi)存大小
Retained Size: GC 回收能收走的內(nèi)存大小
轉(zhuǎn)換 profile 文件格式
將 export 導(dǎo)出的 dprof 文件轉(zhuǎn)換為 Mat 的 dprof 文件
cd /d 進(jìn)入到 Android sdk/platform-tools/hprof-conv.exe
//轉(zhuǎn)換命令 hprof-conv -z src des
D:AndroidAndroidDeveloper-sdkandroid-sdk-windowsplatform-tools>hprof-conv -z D: emp_ emp_6.hprof D: emp_memory6.hprod
下載 Mat 工具
打開 MemoryAnalyzer.exe 點(diǎn)擊左上角 File 菜單中的 Open Heap Dupm
查看內(nèi)存泄漏中的 GC Roots 強(qiáng)引用
這里我們得知是一個(gè) ilsLoginListener 引用了 LoginView,我們來(lái)看下代碼最后怎么解決的。
代碼中我們找到了 LoginView 這個(gè)類,發(fā)現(xiàn)是一個(gè)單例中的回調(diào)引起的內(nèi)存泄漏,下面怎么解決勒,請(qǐng)看第七小點(diǎn)。
2種解決單例中的內(nèi)存泄漏
將引用置為 null
/**
* 銷毀監(jiān)聽
*/
public void unRemoveRegisterListener(){
mMessageController.unBindListener();
}
public void unBindListener(){
if (listener != null){
listener = null;
}
}
使用弱引用
//將監(jiān)聽器放入弱引用中
WeakReference listenerWeakReference = new WeakReference<>(listener);
//從弱引用中取出回調(diào)
listenerWeakReference.get();
通過(guò)第七小點(diǎn)就能完美的解決單例中回調(diào)引起的內(nèi)存泄漏。
單例
示例 :
public class AppManager {
private static AppManager sInstance;
private CallBack mCallBack;
private Context mContext;
private AppManager(Context context) {
this.mContext = context;
}
public static AppManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppManager(context);
}
return sInstance;
}
public void addCallBack(CallBack call){
mCallBack = call;
}
}
通過(guò)上面的單列,如果 context 傳入的是 Activity , Service 的 this,那么就會(huì)導(dǎo)致內(nèi)存泄漏。
以 Activity 為例,當(dāng) Activity 調(diào)用 getInstance 傳入 this ,那么 sInstance 就會(huì)持有 Activity 的引用,當(dāng) Activity 需要關(guān)閉的時(shí)候需要 回收的時(shí)候,發(fā)現(xiàn) sInstance 還持有 沒有用的 Activity 引用,導(dǎo)致 Activity 無(wú)法被 GC 回收,就會(huì)造成內(nèi)存泄漏
addCallBack(CallBack call) 這樣寫看起來(lái)是沒有毛病的。但是當(dāng)這樣調(diào)用在看一下勒。
//在 Activity 中實(shí)現(xiàn)單例的回調(diào)
AppManager.getInstance(getAppcationContext()).addCallBack(new CallBack(){
@Override
public void onStart(){
}
});
這里的 new CallBack() 匿名內(nèi)部類 默認(rèn)持有外部的引用,造成 CallBack 釋放不了,那么怎么解決了,請(qǐng)看下面解決方法
解決方法:
getInstance(Context context) context 都傳入 Appcation 級(jí)別的 Context,或者實(shí)在是需要傳入 Activity 的引用就用 WeakReference 這種形式。
匿名內(nèi)部類建議大家多帶帶寫一個(gè)文件或者
public void addCallBack(CallBack call){
WeakReference mCallBack= new WeakReference(call);
}
Handler
示例:
//在 Activity 中實(shí)現(xiàn) Handler
class MyHandler extends Handler{
private Activity m;
public MyHandler(Activity activity){
m=activity;
}
// class.....
}
這里的 MyHandler 持有 activity 的引用,當(dāng) Activity 銷毀的時(shí)候,導(dǎo)致 GC 不會(huì)回收造成 內(nèi)存泄漏。
解決方法:
1.使用靜態(tài)內(nèi)部類 + 弱引用
2.在 Activity onDestoty() 中處理 removeCallbacksAndMessages()
@Override
protected void onDestroy() {
super.onDestroy();
if(null != handler){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
靜態(tài)變量
示例:
public class MainActivity extends AppCompatActivity {
private static Police sPolice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sPolice != null) {
sPolice = new Police(this);
}
}
}
class Police {
public Police(Activity activity) {
}
}
這里 Police 持有 activity 的引用,會(huì)造成 activity 得不到釋放,導(dǎo)致內(nèi)存泄漏。
解決方法:
//1. sPolice 在 onDestory()中 sPolice = null; //2. 在 Police 構(gòu)造函數(shù)中 將強(qiáng)引用 to 弱引用;
非靜態(tài)內(nèi)部類
參考 第二點(diǎn) Handler 的處理方式
匿名內(nèi)部類
示例:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(){
@Override
public void run() {
super.run();
}
};
}
}
很多初學(xué)者都會(huì)像上面這樣新建線程和異步任務(wù),殊不知這樣的寫法非常地不友好,這種方式新建的子線程Thread和AsyncTask都是匿名內(nèi)部類對(duì)象,默認(rèn)就隱式的持有外部Activity的引用,導(dǎo)致Activity內(nèi)存泄露。
解決方法:
//靜態(tài)內(nèi)部類 + 弱引用
//多帶帶寫一個(gè)文件 + onDestory = null;
未取消注冊(cè)或回調(diào)
示例:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerReceiver(mReceiver, new IntentFilter());
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO ------
}
};
}
在注冊(cè)觀察則模式的時(shí)候,如果不及時(shí)取消也會(huì)造成內(nèi)存泄露。比如使用Retrofit + RxJava注冊(cè)網(wǎng)絡(luò)請(qǐng)求的觀察者回調(diào),同樣作為匿名內(nèi)部類持有外部引用,所以需要記得在不用或者銷毀的時(shí)候取消注冊(cè)。
解決方法:
//Activity 中實(shí)現(xiàn) onDestory()反注冊(cè)廣播得到釋放
@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(mReceiver);
}
定時(shí)任務(wù)
示例:
public class MainActivity extends AppCompatActivity {
/**模擬計(jì)數(shù)*/
private int mCount = 1;
private Timer mTimer;
private TimerTask mTimerTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
mTimer.schedule(mTimerTask, 1000, 1000);
}
private void init() {
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
addCount();
}
});
}
};
}
private void addCount() {
mCount += 1;
}
}
當(dāng)我們Activity銷毀的時(shí),有可能Timer還在繼續(xù)等待執(zhí)行TimerTask,它持有Activity 的引用不能被 GC 回收,因此當(dāng)我們 Activity 銷毀的時(shí)候要立即cancel掉Timer和TimerTask,以避免發(fā)生內(nèi)存泄漏。
解決方法:
//當(dāng) Activity 關(guān)閉的時(shí)候,停止一切正在進(jìn)行中的定時(shí)任務(wù),避免造成內(nèi)存泄漏。
private void stopTimer() {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer();
}
資源未關(guān)閉
示例:
ArrayList,HashMap,IO,File,SqLite,Cursor 等資源用完一定要記得 clear remove 等關(guān)閉一系列對(duì)資源的操作。
解決方法:
用完即刻銷毀
屬性動(dòng)畫
示例:
動(dòng)畫同樣是一個(gè)耗時(shí)任務(wù),比如在 Activity 中啟動(dòng)了屬性動(dòng)畫 (ObjectAnimator) ,但是在銷毀的時(shí)候,沒有調(diào)用 cancle 方法,雖然我們看不到動(dòng)畫了,但是這個(gè)動(dòng)畫依然會(huì)不斷地播放下去,動(dòng)畫引用所在的控件,所在的控件引用 Activity ,這就造成 Activity 無(wú)法正常釋放。因此同樣要在Activity 銷毀的時(shí)候 cancel 掉屬性動(dòng)畫,避免發(fā)生內(nèi)存泄漏。
解決方法:
@Override
protected void onDestroy() {
super.onDestroy();
//當(dāng)關(guān)閉 Activity 的時(shí)候記得關(guān)閉動(dòng)畫的操作
mAnimator.cancel();
}
Android 源碼或者第三方 SDK
示例:
//如果在開發(fā)調(diào)試中遇見 Android 源碼或者 第三方 SDK 持有了我們當(dāng)前的 Activity 或者其它類,那么現(xiàn)在怎么辦了。
解決方法:
//當(dāng)前是通過(guò) Java 中的反射找到某個(gè)類或者成員,來(lái)進(jìn)行手動(dòng) = null 的操作。
內(nèi)存頻繁的分配與回收,(分配速度大于回收速度時(shí)) 最終產(chǎn)生 OOM 。
也許下面的錄屏更能解釋什么是內(nèi)存抖動(dòng)
可以看出當(dāng)我點(diǎn)擊了一下 Button 內(nèi)存就頻繁的創(chuàng)建并回收(注意看垃圾桶)。
那么我們找出代碼中具體那一塊出現(xiàn)問(wèn)題了勒,請(qǐng)看下面一段錄屏
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imPrettySureSortingIsFree();
}
});
/**
* 排序后打印二維數(shù)組,一行行打印
*/
public void imPrettySureSortingIsFree() {
int dimension = 300;
int[][] lotsOfInts = new int[dimension][dimension];
Random randomGenerator = new Random();
for (int i = 0; i < lotsOfInts.length; i++) {
for (int j = 0; j < lotsOfInts[i].length; j++) {
lotsOfInts[i][j] = randomGenerator.nextInt();
}
}
for (int i = 0; i < lotsOfInts.length; i++) {
String rowAsStr = "";
//排序
int[] sorted = getSorted(lotsOfInts[i]);
//拼接打印
for (int j = 0; j < lotsOfInts[i].length; j++) {
rowAsStr += sorted[j];
if (j < (lotsOfInts[i].length - 1)) {
rowAsStr += ", ";
}
}
Log.i("ricky", "Row " + i + ": " + rowAsStr);
}
}
最后我們之后是 onClick 中的 imPrettySureSortingIsFree() 函數(shù)里面的 rowAsStr += sorted[j]; 字符串拼接造成的 內(nèi)存抖動(dòng) ,因?yàn)槊看纹唇右粋€(gè) String 都會(huì)申請(qǐng)一塊新的堆內(nèi)存,那么怎么解決這個(gè)頻繁開辟內(nèi)存的問(wèn)題了。其實(shí)在 Java 中有 2 個(gè)更好的 API 對(duì) String 的操作很友好,相信應(yīng)該有人猜到了吧。沒錯(cuò)就是將 此處的 String 換成 StringBuffer 或者 StringBuilder,就能很完美的解決字符串拼接造成的內(nèi)存抖動(dòng)問(wèn)題。
修改后
/**
* 打印二維數(shù)組,一行行打印
*/
public void imPrettySureSortingIsFree() {
int dimension = 300;
int[][] lotsOfInts = new int[dimension][dimension];
Random randomGenerator = new Random();
for(int i = 0; i < lotsOfInts.length; i++) {
for (int j = 0; j < lotsOfInts[i].length; j++) {
lotsOfInts[i][j] = randomGenerator.nextInt();
}
}
// 使用StringBuilder完成輸出,我們只需要?jiǎng)?chuàng)建一個(gè)字符串即可, 不需要浪費(fèi)過(guò)多的內(nèi)存
StringBuilder sb = new StringBuilder();
String rowAsStr = "";
for(int i = 0; i < lotsOfInts.length; i++) {
// 清除上一行
sb.delete(0, rowAsStr.length());
//排序
int[] sorted = getSorted(lotsOfInts[i]);
//拼接打印
for (int j = 0; j < lotsOfInts[i].length; j++) {
sb.append(sorted[j]);
if(j < (lotsOfInts[i].length - 1)){
sb.append(", ");
}
}
rowAsStr = sb.toString();
Log.i("jason", "Row " + i + ": " + rowAsStr);
}
}
這里可以看見沒有垃圾桶出現(xiàn),說(shuō)明內(nèi)存抖動(dòng)解決了。
注意: 實(shí)際開發(fā)中如果在 LogCat 中發(fā)現(xiàn)有這些 Log 說(shuō)明也發(fā)生了 內(nèi)存抖動(dòng) (Log 中出現(xiàn) concurrent copying GC freed ....)
回收算法ps:我覺得這個(gè)只是為了應(yīng)付面試,那么可以參考這里,我也只了解概念這里就不用在多寫了,點(diǎn)擊看這個(gè)帖子吧
也可以參考掘金的這一篇 GC 回收算法
標(biāo)記清除算法 Mark-Sweep 復(fù)制算法 Copying 標(biāo)記壓縮算法 Mark-Compact 分代收集算法 總結(jié) (只要養(yǎng)成這樣的習(xí)慣,至少可以避免 90 % 以上不會(huì)造成內(nèi)存異常)
數(shù)據(jù)類型: 不要使用比需求更占用空間的基本數(shù)據(jù)類型
循環(huán)盡量用 foreach ,少用 iterator, 自動(dòng)裝箱也盡量少用
數(shù)據(jù)結(jié)構(gòu)與算法的解度處理 (數(shù)組,鏈表,棧樹,樹,圖)
數(shù)據(jù)量千級(jí)以內(nèi)可以使用 Sparse 數(shù)組 (Key為整數(shù)),ArrayMap (Key 為對(duì)象) 雖然性能不如 HashMap ,但節(jié)約內(nèi)存。
枚舉優(yōu)化
缺點(diǎn):
每一個(gè)枚舉值都是一個(gè)單例對(duì)象,在使用它時(shí)會(huì)增加額外的內(nèi)存消耗,所以枚舉相比與 Integer 和 String 會(huì)占用更多的內(nèi)存
較多的使用 Enum 會(huì)增加 DEX 文件的大小,會(huì)造成運(yùn)行時(shí)更多的 IO 開銷,使我們的應(yīng)用需要更多的空間
特別是分 Dex 多的大型 APP,枚舉的初始化很容易導(dǎo)致 ANR
優(yōu)化后的代碼:可以直接限定傳入的參數(shù)個(gè)數(shù)
public class SHAPE {
public static final int TYPE_0=0;
public static final int TYPE_1=1;
public static final int TYPE_2=2;
public static final int TYPE_3=3;
@IntDef(flag=true,value={TYPE_0,TYPE_1,TYPE_2,TYPE_3})
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Model{
}
private @Model int value=TYPE_0;
public void setShape(@Model int value){
this.value=value;
}
@Model
public int getShape(){
return this.value;
}
}
static , static final 的問(wèn)題
static 會(huì)由編譯器調(diào)用 clinit 方法進(jìn)行初始化
static final 不需要進(jìn)行初始化工作,打包在 dex 文件中可以直接調(diào)用,并不會(huì)在類初始化申請(qǐng)內(nèi)存
基本數(shù)據(jù)類型的成員,可以全寫成 static final
字符串的拼接盡量少用 +=
重復(fù)申請(qǐng)內(nèi)存問(wèn)題
同一個(gè)方法多次調(diào)用,如遞歸函數(shù) ,回調(diào)函數(shù)中 new 對(duì)象
不要在 onMeause() onLayout() ,onDraw() 中去刷新UI(requestLayout)
避免 GC 回收將來(lái)要重新使用的對(duì)象 (內(nèi)存設(shè)計(jì)模式對(duì)象池 + LRU 算法)
Activity 組件泄漏
非業(yè)務(wù)需要不要把 activity 的上下文做參數(shù)傳遞,可以傳遞 application 的上下文
非靜態(tài)內(nèi)部類和匿名內(nèi)部?jī)?nèi)會(huì)持有 activity 引用(靜態(tài)內(nèi)部類 或者 多帶帶寫文件)
單例模式中回調(diào)持有 activity 引用(弱引用)
handler.postDelayed() 問(wèn)題
如果開啟的線程需要傳入?yún)?shù),用弱引接收可解決問(wèn)題
handler 記得清除 removeCallbacksAndMessages(null)
Service 耗時(shí)操作盡量使用 IntentService,而不是 Service
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7402.html
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
閱讀 1320·2021-10-08 10:04
閱讀 1953·2021-09-04 16:40
閱讀 2564·2019-08-30 13:21
閱讀 2307·2019-08-29 15:10
閱讀 2886·2019-08-29 12:35
閱讀 1219·2019-08-26 17:41
閱讀 3086·2019-08-26 17:03
閱讀 1190·2019-08-26 12:01