我們在深入Java核心系列文章中給大家講過JVM中的棧和局部變量
在做Java開發的時候常用的JVM內存管理有兩種
一種是堆內存
一種是棧內存
堆內存主要用來存儲程序在運行時創建或實例化的對象與變量
例如
我們通過new MyClass()創建的類MyClass的對象
而棧內存則是用來存儲程序代碼中聲明為靜態(或非靜態)的方法
下面我給大家舉個例子
就拿上面的例子來說
放在棧內存中的有
main
makeThings
放在堆內存中有
Test
list
object
JVM中對象的生命周期大致可以分為
個階段
創建階段
應用階段
不可視階段
不可到達階段
可收集階段
終結階段與釋放階段
創建階段
(
)為對象分配存儲空間
(
)開始構造對象
(
)遞歸調用其超類的構造方法
(
)進行對象實力初始化與變量初始化
(
)執行構造方法體
還有就是你在創建對象的時候需要注意的地方
(
)避免在循環體中創建對象
即使該對象占用內存空間不大
(
)盡量及時使對象符合垃圾回收標准
(
)不要采用過深的繼承層次
(
)訪問本地變量優於訪問類中的變量
應用階段
在應用階段涉及到
個引用
(
)強引用
是指JVM內存管理器從根引用集合出發遍尋堆中所有到達對象的路徑
(
)軟引用
是具有較強的引用功能
只有當內存不夠的時候
才回收這類內存
因此內存足夠的時候
不會被回收
(
)弱引用
弱引用與軟引用對象的最大不同在於
GC在進行回收時
需要通過算法檢查是否回收軟引用對象
而對於弱引用來說
GC總是進行回收
(
)虛引用
主要用於輔助finalize函數的使用
虛引用主要適用於以某種比Java終結機制更靈活的方式調度pre
mortem清除操作
不可視階段
先看一段代碼
如果一個對象已使用完了
應該主動將其設置為null
可以在上面的代碼行obj
doSomething();下添加代碼行obj=null;這樣一行代碼強制將obj對象置為空值
這樣做的意義就是幫助JVM及時的發現這個垃圾對象
並且可以及時的回收該對象占用的系統資源
不可到達階段
處於不可到達階段的對象
在虛擬機所管理的對象引用根集合中再也找不到直接或間接的強引用
這些對象通常是指多有線程棧中的臨時變量
所有已裝載的類的靜態變量或者對本地代碼接口(JNI)引用
可收集階段
終結階段與釋放階段
當對象處於這個階段的時候
可能處於下面三種情況
(
)垃圾回收器發現該對象已經不可到達
(
)finalize方法已經被執行
(
)對象空間已被重用
當對象處於上面三種清空的時候
虛擬機就可以直接將該對象回收了
析構方法finalize
前面我們說了JVM的垃圾回收機制和JVM中對象的生命周期
今天給大家講個方法
叫做析構方法finalize
我想搞過C++的人都知道
而且是內存管理技術中相當重要的一部分
但是
在Java中好像沒有這個概念
這是因為
理論上JVM負責對象的析構(銷毀與回收)工作
finalize是Object類中的一個方法
並且是protected
由於所有的類都繼承了Object對象
因此
就都隱式的繼承了改方法
不過可以重寫這個方法
如果重寫此方法
最後一句必須寫上super
finalize()語句
因為finalize方法沒有自動實現遞歸調用
那我們在什麼時候要重寫它呢?當有一些不容易控制並且非常重要的資源時
要放到finalize方法中
例如
一些I/O的操作
數據的連接等等
這些資源的釋放對整個應用程序是非常關鍵的
我先讓大家看一段代碼
finalize方法最終是由JVM中的垃圾回收器調用的
由於垃圾回收器調用finalize的時間是不確定或者不及時的
調用時機對我們來說是不可控的
因此我們可以在自己的類中聲明一個destory()方法
在這個方法中添加釋放系統資源的處理代碼
但是還是建議你將對destroy()方法的調用放入當前類的finalize()方法體中
因為這樣做更保險
更安全
靜態變量
我們知道類中的靜態變量在程序運行期間
其內存空間對所有該類的對象實例而言是共享的
為了節省系統內存開銷
共享資源
應該將一些變量聲明為靜態變量
通過下面的例子
你就會發現有什麼不同
代碼一
代碼二
我想大家應該發現上面那兩個類的區別了吧!
代碼一會在內存中保存
個weeks的副本
而代碼二則在內存中保存
個weeks的副本
然後共享該副本
這樣的話就不會造成內存的浪費
雖然靜態的變量能節約大量的內存
但是並不是所有的地方都適合用
建議大家在下列條件都符合的情況下
盡量用靜態變量
(
)變量所包含的對象體積較大
占用內存較多
(
)變量所包含的對象生命周期較長
(
)變量所包含的對象數據穩定
(
)該類的對象實例有對該變量所包含的對象的共享需求
如果變量不具備上述特點
建議不要輕易使用靜態變量
以免弄巧成拙
最後
再提一點內存的優化
就是有關對象的重用
比如
對象池和數據庫連接池等
那樣的話
是很節約內存空間的
不過
在用的時候要考慮各個方面
比如
運行環境的內存資源的限制等
為了防止對象池中的對象過多
要記得清除
內存管理有許多技巧和方式
其實內存管理有許多技巧和方式
在這
我給大家介紹一下
(
)要盡早的釋放無用對象的引用
如果
該對象不用了
你可以把它設置為null
但要注意
如果該對象是某方法的返回值
千萬不要這樣處理
否則你從該方法中得到的返回值永遠為空
而且這種錯誤不易被發現
因此這時很難及時抓住
排除NullPointerException異常
(
)盡量少用finalize函數
因為它會加大GC的工作量
因此盡量少用finalize方式回收資源
(
)如果需要使用經常用到的圖片
可以使用soft應用類型(也就是轉換為軟引用類型)
它可以盡可能將圖片保存在內存中
供程序調用
而不引起OutOfMemory
(
)注意集合數據類型
包括數組
樹
圖
鏈表等數據結構
這些數據結構對於GC來說
回收更為復雜
另外
要注意那些全局變量
靜態變量
這些對象往往容易引起懸掛對象
造成內存浪費
(
)盡量避免在類的默認構造器中創建
初始化大量的對象
防止在調用其子類的構造器時造成不必要的內存資源浪費
(
)盡量避免強制系統做垃圾內存回收(通過顯式調用方法System
gc())
增長系統做垃圾回收的最終時間
降低系統性能
(
)盡量避免顯式申請數組空間
當不得不顯式申請數組空間時盡量准確的估計出其合理值
以免造成不必要的系統內存開銷
(
)盡量在做遠程方法調用(RMI)類應用開發時使用瞬間值變量
除非遠程調用端需要獲取該瞬間值變量的值
(
)盡量在合適的場景下使用對象池技術以提高系統的性能
縮減系統內存開銷
但是要注意對象池的尺寸不易過大
及時清除無效對象釋放內存資源
綜合考慮應用運行環境的內存資源限制
避免過高估計運行環境所提供內存資源的數量
雖然
這些技巧提高不了多少性能
但是
在嵌入式開發
或者要求性能比較高的系統中卻很有用
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26573.html