前言
性能優化目的:
1.如何去優化自己的項目,運行更流暢。
現實App進程分配內存空間: 16M 32M 64M
2..以后開發項目的時候就要從一開始把項目做好
內存泄露
什么是內存泄露? 內存不在GC管控之內
當一個對象已經不需要再使用時,本該被回收時,而有另外一個正在使用的對象持有它的引用從而導致對象不能被回收。這種導致了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄露。
不是所有指令都執行得又快又好,下面介紹內存及它如何影響系統運行。普遍認為,多數程序語言接近硬件或高性能,如C、C++和Fortran,通常程序員會自己管理內存,高手工程師對內存的分配,會慎重處理,并在未來結束使用時再次分配,一旦確認何時及怎樣分配內存,內存管理的品質就依賴于工程師的技能跟效率。實際情況是工程師們,不都會去追蹤那零碎的內存碎片。程序開發是個混亂又瘋狂的過程,內存通常都沒辦法完全被釋放,這些被囚禁的內存叫內存泄露。
內存泄露占用了大量資源,這些資源其實可以更好地使用,為減少泄露引起的混亂、負擔、甚至資金損失,便有了內存管理語言。
這些語言在運行時跟蹤內存分配,以便當程序不再需要時釋放系統內存,完全不用工程師親自操作,這些內存回收藝術或科學,在內存管理環節下叫垃圾清理。這個設計概念在1959年,當初為了解決lisp語言問題,由John McCarthy發明的。
垃圾清理的基本概念有:
第一,找到未來無法存取的數據,例如所有不受指令操控的內存。
第二,回收被利用過的資源。原理簡單,但是兩百萬行編碼,跟4gigs的分配,在實際操作時卻非常困難。如果在程序中有20000個對象分配,垃圾清理會讓人困惑,哪一個是沒用的?或者,何時啟動垃圾清理釋放內存?這些問題其實很復雜。好在50年來,我們找到了解決問題的方法,就是Android Runtime中的垃圾清理。比McCarthy最初的方法更高級,速度快且是非侵入性的。經由分配類型,及系統如何有效地組織分配以利GC的運行,并作為新的配置。所有影響android runtime的內存堆都被分割到空間中,根據這些特點,哪些數據適合放到什么空間,取決于哪個Android版本。
了解內存分配的幾種策略:
1.靜態的
在編譯時就能確定每個數據目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內存空間.這種分配策略要求程序代碼中不允許有可變代碼結構(比如可變數組的存在),也不允許有嵌套或者遞歸結構的出現,因為它們都會導致編譯程序無法計算準確的存儲空間需求。
靜態的存儲區,內存在分配的時候就已經分配好了,這塊的內存在程序在整個運行期間內一直存在。
2.棧式的
棧式存儲分配也可稱為動態存儲分配,是由一個類似于堆棧的運行棧來實現的.和靜態存儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是完全未知的,只有到運行的時候才能夠知道,但是規定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區大小才能夠為其分配內存.和我們在數據結構所熟知的棧一樣,棧式存儲分配按照先進后出的原則進行分配。
在執行函數(方法)時,函數一些內部變量的存儲都可以放在棧上創建,函數執行結束的時候這些存儲單元就會自動被釋放掉。棧內存包括分配的運算速度很快,因為內置在處理器里面的。當然容量有限。
3.堆式的
堆式存儲分配則專門負責在編譯時或運行時模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋放.
在C/C++可能需要自己負責釋放(java里面直接額依賴GC機制)
棧式和堆式區別:
從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的.
heap:是由malloc之類函數分配的空間所在地。地址是由低向高增長的。
stack:是自動分配變量,以及函數調用的時候所使用的一些空間。地址是由高向低減少的。
使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。
使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。
public class Main{
int a = 1;//堆里面
Student s = new Student();//堆里面
public void XXX(){//堆里面
int b = 1;//棧里面
Student s2 = new Student();
}
}
1.成員變量全部存儲在堆中(包括基本數據類型,引用和引用的對象實體) --- 因為它們屬于類,類最終還是要被new出來。
2.局部變量的基本數據類型和引用存儲于棧當中,引用的對象實體存儲于在堆中。-----因為他們屬于方法當中的變量,生命周期會隨著方法一起結束。
我們所討論的內存泄漏,主要是討論堆存儲,它存放的是引用指向的對象實體。
有時候確實會有一種情況:當需要的時候可以訪問,當不需要的時候可以被回收也可以被暫時保存以備重復使用。
比如:ListView或者GridView、REcyclerView加載大量數據或者圖片的時候,
圖片非常占用內存,一定要管理好內存,不然很容易內存溢出。
滑出去的圖片就回收,節省內存。看ListView的源碼----回收對象,還會重用ConvertView。
如果用戶反復滑動或者下面還有同樣的圖片,就會造成多次重復IO(很耗時),
那么需要緩存---平衡好內存大小和IO,算法和一些特殊的java類。
算法:lrucache(最近最少使用先回收)
特殊的java類:利于回收,StrongReference,SoftReference,WeakReference,PhatomReference
StrongReference --- 強引用:
StrongReference 是 Java的默認引用實現, 它會盡可能長時間的存活于 JVM 內, 當沒有任何對象指向它時 GC 執行后將會被回收
回收時機:從不回收 使用:對象的一般保存 生命周期:JVM停止的時候才會終止
SoftReference --- 軟引用
SoftReference 于 WeakReference 的特性基本一致, 最大的區別在于 SoftReference 會盡可能長的保留引用直到 JVM 內存不足時才會被回收(虛擬機保證), 這一特性使得 SoftReference 非常適合緩存應用
回收時機:當內存不足的時候;使用:SoftReference<String>結合ReferenceQueue構造有效期短;生命周期:內存不足時終止
WeakReference --- 弱引用
WeakReference 是一個弱引用, 當所引用的對象在 JVM 內不再有強引用時, GC 后 weak reference 將會被自動回
回收時機:在垃圾回收的時候;使用:同軟引用; 生命周期:GC后終止
PhatomReference --- 虛引用
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅>持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在于:虛引用必須和引用>隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對
象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。
回收時機:在垃圾回收的時候;使用:合ReferenceQueue來跟蹤對象被垃圾回收期回收的活動; 生命周期:GC后終止
開發時,為了防止內存溢出,處理一些比較占用內存大并且生命周期長的對象的時候,可以盡量使用軟引用和弱引用。軟引用比LRU算法更加任性,回收量是比較大的,你無法控制回收哪些對象。
比如使用場景:默認頭像、默認圖標。
ListView或者GridView、RecyclerView要使用內部緩存+外部緩存(SD卡)
-----------------------------內存泄漏例子--------------------------
單例模式導致內存對象無法釋放而導致內存泄漏
public class CommonUtils {
private static CommonUtils instance;
private Context context;
private CommonUtils(Context context) {
this.context = context;
}
public static CommonUtils getInstance(Context context) {
if (instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommonUtils commonUtils = CommonUtils.getInstance(this);
}
}
運行這個簡單的單例模式程序,進行多次橫豎屏切換,發現可用內存越來越小,存在內存泄漏現象,最終導致內存溢出。
分析原因:
點擊Monitors->Memory中 Dump Java Heap 采集記錄各個類內存使用情況,如下圖:
根據圖我們發現,進行多次橫豎屏切換時,生產了9個MainActivity。多個MainActivity所占用的內存資源沒有被GC及時回收,導致內存泄漏。
總結
我們能用Application的context就用Application的
CommonUtils 生命周期是跟Application進程同生同死。
特別感謝
動腦學院Ricky