Java虛擬機內存原型
寄存器:咱們在程序沒法控制
棧:存放基本類型的數據和對象的引用,但對象自己不存放在棧中,而是堆中
存取速度比堆塊,僅次於寄存器,棧數據能夠共享,棧的數據大小與生存期必須是肯定的,缺少靈活性。
堆:存放new產生的數據
能夠動態分配內存大小,生存期也沒必要事先告訴編譯器,由於它在運行時動態分配內存,Java的垃圾收集器會自動收走這些再也不使用的數據,但缺點是,因爲在運行時分配內存,存取速度較慢
靜態域:存放在對象中用static定義的靜態成員
常量池:存放常量(利用final關鍵字修飾的)
非RAM存儲:硬盤等永久存儲空間html
Java引用的種類
>對象在內存中狀態
對於JVM的垃圾回收機制來講,若是一個對象,沒有一個引用指向它,那麼它就被認爲是一個垃圾。那該對象就會回收。能夠把JVM內存中對象引用理解成一種有向圖,把引用變量、對象都當成有向圖的頂點,將引用關係當成圖的有向邊(注意:有向邊老是從引用變量指向被引用的Java對象)
一、可達狀態
當一個對象被建立後,有一個或一個以上的引用變量引用它,則這個對象在程序中處於可達狀態,程序能夠經過引用變量來調用該對象的方法和屬性。
二、可恢復狀態
若是程序中某個對象再也不有任何引用變量引用它,它就進入了可恢復狀態,在這個狀態下,系統的垃圾回收機制準備回收該對象所佔用的內存空間,在回收該對象以前,系統會調用全部對象的finalize方法進行資源的清理,若是系統在調用finalize方法從新讓一個引用變量引用該對象,則這個對象會再次變爲激活狀態,不然該 對象狀進入不可達狀態。
三、不可達狀態
當對象與全部引用變量的關聯都被切繼,且系統已經調用全部對象的finalize方法依然沒有該對象變成可達狀態,那這個對象將永久性地失去引用,最後變成不可達狀態,只有當一個對象處於不可達狀態時統纔會真正回收該對象所佔有的資源。java
*對象的狀態轉換圖以下:
程序員
>強引用
強引用是Java編程中使用普遍的引用類型,被強引用所引用的Java對象毫不會被垃圾回收,當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題,所以強引用是形成Java內存泄漏的主要緣由之一
*代碼示例算法
class Person
{
String name;
int age; public Person(String name,int age) { this.name=name; this.age=age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class ReferenceTest { public static void main(String[] args) {
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
運行結果(結果說明內存仍是足夠的)
咱們來把修改java虛擬機內存,把堆內存減小到2m
(操做:程序右鍵選屬性->run/debug settings->選中應用程序->編輯->Arguments->VM arguments輸入框輸入 -Xmx2m -Xms2m )
再運行(程序由於內存不足而停止)
數據庫
>軟引用
軟引用經過SoftReference類來實現,當系統內存空間足夠,軟引用的對象不會被系統回收,程序也可使用該對象,當系統內存不足時,系統將會回收
*代碼示例編程
運行結果(結果說明內存足夠的)
修改java虛擬機內存,把堆內存減小到2m,再運行
從運行結果能夠看出,內存不足時,垃圾回收機制會回收SoftReference引用的對象數組
>弱引用
弱引用與軟引用有點類似,區別是弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
*代碼示例緩存
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
內存分配圖
運行結果
從運行結果看出,當系統垃圾回收機制啓動後,弱引用的對象就會被清除掉。null表面該對象已經被清除了安全
>虛引用
虛引用經過PhantomReference類實現,相似沒有引用,主要做用是跟蹤對象被垃圾回收的狀態,程序能夠經過檢查與虛引用關聯的引用隊列中是否已經包含指定的虛引用,從而瞭解虛引用所引用的對象是否即將被回收,虛引用不能單獨使用,必需要和引用隊列(ReferenceQueue)聯合使用
*代碼示例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
運行結果
從運行結果能夠看出,在未強制進行垃圾回收,程序輸出null,說明系統沒法經過虛引用訪問被引用的對象,當程序強制回收垃圾後,虛引用引用的對象被回收,而後該引用會添加到關聯的引用隊列中,因此輸出true,因此說程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收
Java的內存泄漏
無用對象(再也不使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而形成的內存空間的浪費,就是內存泄漏
一、靜態變量引發內存泄露:
根據分代回收機制(後面有講),JVM會將程序中obj引用變量存在Permanent代裏,這致使Object對象一直有效,從而使obj引用的Object得不到回收
例:
class Person { static Object obj=new Object(); }
二、當集合裏面的對象屬性被修改後,再調用remove()方法時不起做用。
例:
public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孫悟空","pwd2",26); Person p3 = new Person("豬八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("總共有:"+set.size()+" 個元素!");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
三、監聽器
在java 編程中,咱們都須要和監聽器打交道,一般一個應用當中會用到不少監聽器,咱們會調用一個控件的諸如addXXXListener()等方法來增長監聽器,但每每在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增長了內存泄漏的機會。
四、各類鏈接
好比數據庫鏈接(dataSourse.getConnection()),網絡鏈接(socket)和io鏈接,除非其顯式的調用了其close()方法將其鏈接關閉,不然是不會自動被GC 回收的。對於Resultset 和Statement 對象能夠不進行顯式回收,但Connection 必定要顯式回收,由於Connection 在任什麼時候候都沒法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會當即爲NULL。可是若是使用鏈接池,狀況就不同了,除了要顯式地關閉鏈接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另一個也會關閉),不然就會形成大量的Statement 對象沒法釋放,從而引發內存泄漏。這種狀況下通常都會在try裏面去的鏈接,在finally裏面釋放鏈接。
五、內部類和外部模塊等的引用
內部類的引用是比較容易遺忘的一種,並且一旦沒釋放可能致使一系列的後繼類對象沒有釋放。此外程序員還要當心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如:
public void registerMsg(Object b);
這種調用就要很是當心了,傳入了一個對象,極可能模塊B就保持了對該對象的引用,這時候就須要注意模塊B 是否提供相應的操做去除引用。
六、單例模式
單例對象在被初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,致使內存泄露,考慮下面的例子:
class A{
public A(){ B.getInstance().setA(this); } .... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
顯然B採用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想象下若是A是個比較複雜的對象或者集合類型會發生什麼狀況
垃圾回收機制
垃圾回收的基本算法
標記壓縮法
先從根節點開始對全部可達對象作一次標記,但以後,它並不簡單地清除未標記的對象,而是將全部的存活對象壓縮到內存的一端以後,清理邊界外全部的空間。這種方法既避免了碎片的產生,又不須要兩塊相同的內存空間,所以,其性價比比較高。
標記回收法
從「GC Roots」(GC Roots指垃圾收集器的對象,GC會收集那些不是GC roots且沒有被GC roots引用的對象)集合開始,將內存整個遍歷一次,保留全部被GC Roots直接或者間接引用到的對象,而剩下的對象都看成垃圾對待並回收,這個算法須要中斷進程內其餘組件的執行而且可能產生碎片化
複製回收法
將內存分爲大小相等的兩部分(假設A、B兩部分),每次呢只使用其中的一部分(這裏咱們假設爲A區),等這部分用完了,這時候就將這裏面還能活下來的對象複製到另外一部份內存(這裏設爲B區)中,而後把A區中的剩下部分所有清理掉。這樣內存碎片的問題就解決了
![這裏寫圖片描述](http://static.javashuo.com/static/loading.gif)
分代回收法
根據對象的生命週期將內存劃分,而後進行分區管理,在Java虛擬機分代垃圾回收機制中,應用程序可用的堆空間能夠分爲年輕代與老年代,年輕代有被分爲Eden區,From區與To區
分代回收法更詳細連接http://blog.csdn.net/sinat_36246371/article/details/52998505
>堆內存的分代回收
>與垃圾回收的附加選項
下面兩個選項用於設置java虛擬機內存大小
-Xms :設置java虛擬機堆內存的最大容量如java -Xmx256m XxxClass
-Xms :設置java虛擬機堆內存的初始容量,如java -Xms128m XxxClass
下面選項都是關於java垃圾回收的附加選項
-xx:MinHeapFreeRatio =40 :設置java堆內存最小的空閒百分比,默認爲40,如java -xx:MinHeapFreeRadio = 40 XxxClass
-xx:MaxHeapFreeRatio=70 :設置Java堆內存最大的空閒百分比,默認爲70,如java -XX:MaxHeapFreeRatio =70 XxxClass
-xx:NewRatio=2 ;設置Yonng/Old內存的比例,如java -XX:NewRatio=1 XxxClass
-xx:NewSize=size:設置Yonng代內存的默認容量,如java -XX:Newsize=64m XxxClass
-xx:SurvivorRatio = 8;設置Yonng代中eden/survivor的比例,如java -xx:MaxNewSize=128m XxxClass
注意 當設置Young代的內存超過了-Xmx設置的大小時,Young設置的內存大小將不會起做用,JVM會自動將Young代內存設置爲與-Xmx設置的大小相等。
-XX:PermSIze=size;設置Permnanent代內存的默認容量,如java –XX:PermSize=128m XxxClass
-XX:MaxPermSize=64m;設置Permanent代內存的最大容量,如java -XX:MaxPermSize=128m XxxClass
>常見垃圾回收器
- 串行回收器(Serial Garbage Collector)
Serial Garbage Collector經過暫停全部應用的線程來工做。它是爲單線程工做環境而設計的。它中使用一個線程來進行垃圾回收。這種暫停應用線程來進行垃圾回收的方式可能不太適應服務器環境。它最適合簡單的命令行程序。
經過 -XX:+UseSerialGC 參數來選用Serial Garbage Collector。
- Parallel Garbage Collector
Parallel Garbage Collector也被稱爲吞吐量收集器(throughput collector)。它是Java虛擬機的默認垃圾收集器。與Serial Garbage Collector不一樣,Parallel Garbage Collector使用多個線程進行垃圾回收。與Serial Garbage Collector類似的地方時,它也是暫停全部的應用線程來進行垃圾回收。
3. CMS Garbage Collector
Concurrent Mark Sweep (CMS) Garbage Collector使用多個線程來掃描堆內存來標記須要回收的實例,而後再清除被標記的實例。CMS Garbage Collector只有在以下兩種情景纔會暫停全部的應用線程:
當標記永久代內存空間中的對象時;
當進行垃圾回收時,堆內存同步發生了一些變化。
相比Parallel Garbage Collector,CMS Garbage Collector使用更多的CPU資源來確保應用有一個更好的吞吐量。若是分配更多的CPU資源能夠得到更好的性能,那麼CMS Garbage Collector是一個更好的選擇,相比Parallel Garbage Collector。
經過 XX:+USeParNewGC 參數來選用CMS Garbage Collector。
內存管理的小技巧
>儘可能使用直接量
當須要使用字符串,還有Byte,Short、integer、Long、Float、Double、Boolean、Character包裝類的實例時,不該該採用new的方式來建立對象,而應該使用直接量來建立它們
應該是String str="hello";
而不是String str=new String("hello");
後者除了在建立一個緩存在字符串緩衝池的「hello」字符串,str所引用的String對象底層還包含一個存放了h、e、l、l、o的char[ ]數組
>使用StringBuilder和StringBuffer進行字符串鏈接
String表明字符序列不可變的字符串,StringBuilderheStringBuffer都表明字符序列可變的字符串
建議
StringBuilder st = new StringBuilder()
不建議
String c = a+b;
由於這樣會在運行的時候生成大量臨時字符串,這些字符串會保存在內存中從而致使程序性能降低
若是使用少許的字符串操做,使用 (+運算符)鏈接字符串;
若是頻繁的對大量字符串進行操做,則使用
1:全局變量或者須要多線程支持則使用StringBuffer;
2:局部變量或者單線程不涉及線程安全則使有StringBuilder
>儘早釋放無用對象的引用
>儘可能少用靜態變量
>避免在常常調用的方法、循環中建立Java對象
>緩存常用的對象
>儘可能不要使用finalize方法
>考慮使用SoftReference
參考:《瘋狂java 突破程序員基本功的16課》