最近打算總結幾篇app性能優化方面的東西,畢竟android弄了這么久,萬一到哪些轉了行,歲月久了就忘記了,純粹當個人筆記。今個是第一篇---性能優化的基本概念,毛主席說了,讓理論先行,理論指導實踐。性能優化的重要性不想再強調,我是個新手,要開始開車了,閱讀本文你會弄清楚或者再次回顧以下問題。
- 1、java的內存區域如何劃分?
- 2、java中的引用有哪些?如何運用?
- 3、什么是內存泄露?內存泄露發生的場景有哪些?
- 4、Garbage Collector(垃圾回收器)什么是垃圾,什么是非垃圾?
看到這,如果你覺得有必要了解一下,請往下讀,否則press back key!!!
問題1、java的內存區域如何劃分?
有兩種說法:
一種說法是分為: 堆(Heap),棧(Stacks)方法區(MethodArea),運行時常量池(RuntimeConstant Pool),本地方法棧(NativeMethod Stacks),PC Register(PC寄存器)。是從抽象的JVM的角度去看的。
另一種說法是分為:堆(Heap),棧(Stacks),數據段(data segment),代碼段(code segment)。則是從操作系統上的進程的角度去看的。
我們按照第一種說法簡單看一下。
Heap/Stack
在這問題中,我們主要要弄清楚,什么是堆,什么是棧,堆棧內存有什么區別?
- Heap內存的分配也叫做動態內存分配,java中運行環境用來分配給對象和JRE類的內存都在堆內存,C/C++有時候可以用malloc或者new來申請分配一個內存。在C/C++可能需要自己負責釋放(java里面直接依賴GC機制)。
- Stack內存是相對于線程Thread而言的, 在執行函數(方法)時,函數一些內部變量的存儲都可以放在棧上面創建,函數執行結束的時候這些存儲單元就會自動被釋放掉。棧內存包括分配的運算速度很快,因為內置在處理器的里面的。當然容量有限。它保存線程中方法中短期存在的變量值和對Heap中對象的引用等.
區別:堆是不連續的內存區域,堆空間比較靈活也特別大。 棧式一塊連續的內存區域,大小是有操作系統覺決定的。堆管理很麻煩,頻繁地new/remove會造成大量的內存碎片,這樣就會慢慢導致效率低下。對于棧的話,他先進后出,進出完全不會產生碎片,運行效率高且穩定。
我們通常說的內存泄露,GC,是針對Heap內存的. 因為Stack內存在函數出棧的時候就銷毀了。
比如說這個類
public class People{
int a = 1;
Student s1 = new Student();
public void XXX(){
int b = 1;
Student s2 = new Student();
}
}
請問a的內存在哪里,b的內存在哪里,s1,s2的內存在哪里?記住下面兩句話。
- 成員變量全部存儲在堆中(包括基本數據類型,引用及引用的對象實體),因為他們屬于類,類對象最終還是要被new出來的。
- 局部變量的基本數據類型和引用存儲于棧當中,引用的對象實體存儲在堆中。因為他們屬于方法當中的變量,生命周期會隨著方法一起結束。
所以答案就是a,s1,s2對象都堆中,b和s2對象引用在棧中。
問題2、java中的引用有哪些?如何運用?
從JDK1.2版本開始,把對象的引用分為四種級別,從而使程序能更加靈活的控制對象的生命周期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
- **強引用(StrongReference) ** 我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。如果一個對象具有強引用,那就類似于必不可少的生活用品,垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。
- **軟引用(SoftReference) ** 如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。
- 弱引用(WeakReference) 在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。 弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
- **虛引用(PhantomReference) ** 如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛 引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在于:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是 否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。
public class Demo1 {
public static void main(String[] args) {
//這就是一個強引用
String str="hello";
//現在我們由上面的強引用創建一個軟引用,所以現在str有兩個引用指向它
SoftReference<String> soft=new SoftReference<String>(str);
str=null;
//可以使用get()得到引用指向的對象
System.out.println(soft.get());//輸出hello
}
}
public class Demo2 {
public static void main(String[] args) {
//這就是一個強引用
String str="hello";
ReferenceQueue<? super String> q=new ReferenceQueue<String>();
//現在我們由上面的強引用創建一個虛引用,所以現在str有兩個引用指向它
PhantomReference<String> p=new PhantomReference<String>(str, q);
//可以使用get()得到引用指向的對象
System.out.println(q.poll());//輸出null
}
}
下面再看一個,首先創建一個Store類,內部定義一個很大的數組,目的是創建對象時,會得到更多的內存,以提高回收的可能性!
public class Store {
public static final int SIZE = 10000;
private double[] arr = new double[SIZE];
private String id;
public Store() {
}
public Store(String id) {
super();
this.id = id;
}
@Override
protected void finalize() throws Throwable {
System.out.println(id + "被回收了");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return id;
}
}
依次創建軟引用,弱引用,虛引用個10個!
public class Demo3 {
public static ReferenceQueue<Store> queue = new ReferenceQueue<Store>();
public static void checkQueue()
{
if(queue!=null)
{
@SuppressWarnings("unchecked")
Reference<Store> ref =(Reference<Store>)queue.poll();
if(ref!=null)
System.out.println(ref+"......"+ref.get());
}
}
public static void main(String[] args) {
HashSet<SoftReference<Store>> hs1 = new HashSet<SoftReference<Store>>();
HashSet<WeakReference<Store>> hs2 = new HashSet<WeakReference<Store>>();
//創建10個軟引用
for(int i=1;i<=10;i++)
{
SoftReference<Store> soft = new SoftReference<Store>(new Store("soft"+i),queue);
System.out.println("create soft"+soft.get());
hs1.add(soft);
}
System.gc();
checkQueue();
//創建10個弱引用
for(int i=1;i<=10;i++)
{
WeakReference<Store> weak = new WeakReference<Store>(new Store("weak"+i),queue);
System.out.println("create weak"+weak.get());
hs2.add(weak);
}
System.gc();
checkQueue();
//創建10個虛引用
HashSet<PhantomReference<Store>> hs3 = new HashSet<PhantomReference<Store>>();
for(int i=1;i<=10;i++)
{
PhantomReference<Store> phantom = new PhantomReference<Store>(new Store("phantom"+i),queue);
System.out.println("create phantom "+phantom.get());
hs3.add(phantom);
}
System.gc();
checkQueue();
}
}
程序執行結果:
在Handler中或者圖片的三級緩存是不是經常會有虛引用出現呢,所以,java四大引用還是了解一下為好。
問題3、什么是內存泄露?內存泄露發生的場景有哪些?
當一個對象已經不需要再使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用,從而就導致對象不能被回收。這種導致了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏。內存泄露問題,在下篇博客中會詳細介紹把內存泄露抓出來。
內存泄露的場景有很多。
非靜態內部類的靜態實例
由于內部類默認持有外部類的引用,而靜態實例屬于類。所以,當外部類被銷毀時,內部類仍然持有外部類的引用,致使外部類無法被GC回收。因此造成內存泄露。類的靜態變量持有大數據對象
靜態變量長期維持到大數據對象的引用,阻止垃圾回收。資源對象未關閉
資源性對象如Cursor、Stream、Socket,Bitmap,應該在使用后及時關閉。未在finally中關閉,會導致異常情況下資源對象未被釋放的隱患。注冊對象未反注冊
我們常常寫很多的Listener,未反注冊會導致觀察者列表里維持著對象的引用,阻止垃圾回收。Handler臨時性內存泄露
Handler通過發送Message與主線程交互,Message發出之后是存儲在MessageQueue中的,有些Message也不是馬上就被處理的。
-Context泄露
這個太多了,不細說,單利模式寫的不恰當就屬于這種。
場景還有很多,我水平有限,最好記住這些常見的場景,在一開始寫代碼的時候,就要規避這些問題。記不住也不要緊,我們關鍵要學會怎么去解決內存泄露。
問題4、Garbage Collector(垃圾回收器)什么是垃圾,什么是非垃圾?
- 什么是GC?
GC 是 garbage collection 的縮寫, 垃圾回收的意思. 也可以是 Garbage Collector, 也就是垃圾回收器.
垃圾回收機制有好幾套算法,java語言規范沒有明確的說明JVM 使用哪種垃圾回收算法,但是任何一種垃圾回收算法一般要做兩件基本事情:(1)發現無用的信息對象;(2)回收將無用對象占用的內存空間。使該空間可被程序再次使用。
有一種算法是根搜索算法
根搜索算法是從離散數學中的圖論引入的,程序把所有的引用關系看作一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點以后,繼續尋找這個節點的引用節點,當所有的引用節點尋找完畢之后,剩余的節點則被認為是沒有被引用到的節點,即無用的節點。如果這個對象是引用可達的, 則稱之為活的(live), 反之, 如果這個對象引用不可達, 則稱之為死的(dead), 也可以稱之為垃圾(garbage).這個引用可達與不可達就是相對于GC Root來說的,在上圖中,左邊4個對象就是活的,右邊兩個就是死的,也就是我們說的可以被回收的垃圾。
OK,到此為止,下篇介紹內存泄露檢測工具Android monitor。