摘 要 本文在分析對象池技術基本原理的基礎上,給出了對象池技術的兩種實現(xiàn)方式。還指出了使用對象池技術時所應注意的問題。
關鍵詞 對象池;對象池技術;Java 對象;性能
Java對象的生命周期分析
Java對象的生命周期大致包括三個階段:對象的創(chuàng)建,對象的使用,對象的清除。因此,對象的生命周期長度可用如下的表達式表示:T = T1 + T2 +T3。其中T1表示對象的創(chuàng)建時間,T2表示對象的使用時間,而T3則表示其清除時間。由此,我們可以看出,只有T2是真正有效的時間,而T1、T3則是對象本身的開銷。下面再看看T1、T3在對象的整個生命周期中所占的比例。
我們知道,Java對象是通過構造函數(shù)來創(chuàng)建的,在這一過程中,該構造函數(shù)鏈中的所有構造函數(shù)也都會被自動調用。另外,默認情況下,調用類的構造函數(shù)時,Java會把變量初始化成確定的值:所有的對象被設置成null,整數(shù)變量(byte、short、int、long)設置成0,float和double變量設置成0.0,邏輯值設置成false。所以用new關鍵字來新建一個對象的時間開銷是很大的,如表1所示。
表1 一些操作所耗費時間的對照表
從表1可以看出,新建一個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而若新建一個數(shù)組所花費的時間就更多了。
再看清除對象的過程。我們知道,Java語言的一個優(yōu)勢,就是Java程序員勿需再像C/C++程序員那樣,顯式地釋放對象,而由稱為垃圾收集器(Garbage Collector)的自動內存管理系統(tǒng),定時或在內存凸現(xiàn)出不足時,自動回收垃圾對象所占的內存。凡事有利總也有弊,這雖然為Java程序設計者提供了極大的方便,但同時它也帶來了較大的性能開銷。這種開銷包括兩方面,首先是對象管理開銷,GC為了能夠正確釋放對象,它必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請、引用、被引用、賦值等。其次,在GC開始回收“垃圾”對象時,系統(tǒng)會暫停應用程序的執(zhí)行,而獨自占用CPU。
因此,如果要改善應用程序的性能,一方面應盡量減少創(chuàng)建新對象的次數(shù);同時,還應盡量減少T1、T3的時間,而這些均可以通過對象池技術來實現(xiàn)。
對象池技術的基本原理
對象池技術基本原理的核心有兩點:緩存和共享,即對于那些被頻繁使用的對象,在使用完后,不立即將它們釋放,而是將它們緩存起來,以供后續(xù)的應用程序重復使用,從而減少創(chuàng)建對象和釋放對象的次數(shù),進而改善應用程序的性能。事實上,由于對象池技術將對象限制在一定的數(shù)量,也有效地減少了應用程序內存上的開銷。
實現(xiàn)一個對象池,一般會涉及到如下的類:
1)對象池工廠(ObjectPoolFactory)類
該類主要用于管理相同類型和設置的對象池(ObjectPool),它一般包含如下兩個方法:
·createPool:用于創(chuàng)建特定類型和設置的對象池;
·destroyPool:用于釋放指定的對象池;
同時為保證ObjectPoolFactory的單一實例,可以采用Singleton設計模式,見下述getInstance方法的實現(xiàn):
2)參數(shù)對象(ParameterObject)類
該類主要用于封裝所創(chuàng)建對象池的一些屬性參數(shù),如池中可存放對象的數(shù)目的最大值(maxCount)、最小值(minCount)等。
3)對象池(ObjectPool)類
用于管理要被池化對象的借出和歸還,并通知PoolableObjectFactory完成相應的工作。它一般包含如下兩個方法:
·getObject:用于從池中借出對象;
·returnObject:將池化對象返回到池中,并通知所有處于等待狀態(tài)的線程;
4)池化對象工廠(PoolableObjectFactory)類
該類主要負責管理池化對象的生命周期,就簡單來說,一般包括對象的創(chuàng)建及銷毀。該類同ObjectPoolFactory一樣,也可將其實現(xiàn)為單實例。
通用對象池的實現(xiàn)
對象池的構造和管理可以按照多種方式實現(xiàn)。最靈活的方式是將池化對象的Class類型在對象池之外指定,即在ObjectPoolFactory類創(chuàng)建對象池時,動態(tài)指定該對象池所池化對象的Class類型,其實現(xiàn)代碼如下:
其中,paraObj參數(shù)用于指定對象池的特征屬性,clsType參數(shù)則指定了該對象池所存放對象的類型。對象池(ObjectPool)創(chuàng)建以后,下面就是利用它來管理對象了,具體實現(xiàn)如下:
從上述代碼可以看出,ObjectPool利用一個java.util.Vector作為可擴展的對象池,并通過它的構造函數(shù)來指定池化對象的Class類型及對象池的一些屬性。在有對象返回到對象池時,它將檢查對象的類型是否正確。當對象池里不再有可用對象時,它或者等待已被使用的池化對象返回池中,或者創(chuàng)建一個新的對象實例。不過,新對象實例的創(chuàng)建并不在ObjectPool類中,而是由PoolableObjectFactory類的createObject方法來完成的,具體實現(xiàn)如下:
這樣,通用對象池的實現(xiàn)就算完成了,下面再看看客戶端(Client)如何來使用它,假定池化對象的Class類型為StringBuffer:
可以看出,通用對象池使用起來還是很方便的,不僅可以方便地避免頻繁創(chuàng)建對象的開銷,而且通用程度高。但遺憾的是,由于需要使用大量的類型定型(cast)操作,再加上一些對Vector類的同步操作,使得它在某些情況下對性能的改進非常有限,尤其對那些創(chuàng)建周期比較短的對象
專用對象池的實現(xiàn)
由于通用對象池的管理開銷比較大,某種程度上抵消了重用對象所帶來的大部分優(yōu)勢。為解決該問題,可以采用專用對象池的方法。即對象池所池化對象的Class類型不是動態(tài)指定的,而是預先就已指定。這樣,它在實現(xiàn)上也會較通用對象池簡單些,可以不要ObjectPoolFactory和PoolableObjectFactory類,而將它們的功能直接融合到ObjectPool類,具體如下(假定被池化對象的Class類型仍為StringBuffer,而用省略號表示的地方,表示代碼同通用對象池的實現(xiàn)):
結束語
恰當?shù)厥褂脤ο蟪丶夹g,能有效地改善應用程序的性能。目前,對象池技術已得到廣泛的應用,如對于網(wǎng)絡和數(shù)據(jù)庫連接這類重量級的對象,一般都會采用對象池技術。但在使用對象池技術時也要注意如下問題:
·并非任何情況下都適合采用對象池技術。基本上,只在重復生成某種對象的操作成為影響性能的關鍵因素的時候,才適合采用對象池技術。而如果進行池化所能帶來的性能提高并不重要的話,還是不采用對象池化技術為佳,以保持代碼的簡明。
·要根據(jù)具體情況正確選擇對象池的實現(xiàn)方式。如果是創(chuàng)建一個公用的對象池技術實現(xiàn)包,或需要在程序中動態(tài)指定所池化對象的Class類型時,才選擇通用對象池。而大部分情況下,采用專用對象池就可以了。
關鍵詞 對象池;對象池技術;Java 對象;性能
Java對象的生命周期分析
Java對象的生命周期大致包括三個階段:對象的創(chuàng)建,對象的使用,對象的清除。因此,對象的生命周期長度可用如下的表達式表示:T = T1 + T2 +T3。其中T1表示對象的創(chuàng)建時間,T2表示對象的使用時間,而T3則表示其清除時間。由此,我們可以看出,只有T2是真正有效的時間,而T1、T3則是對象本身的開銷。下面再看看T1、T3在對象的整個生命周期中所占的比例。
我們知道,Java對象是通過構造函數(shù)來創(chuàng)建的,在這一過程中,該構造函數(shù)鏈中的所有構造函數(shù)也都會被自動調用。另外,默認情況下,調用類的構造函數(shù)時,Java會把變量初始化成確定的值:所有的對象被設置成null,整數(shù)變量(byte、short、int、long)設置成0,float和double變量設置成0.0,邏輯值設置成false。所以用new關鍵字來新建一個對象的時間開銷是很大的,如表1所示。
表1 一些操作所耗費時間的對照表
運算操作 | 示例 | 標準化時間 |
本地賦值 | i = n | 1.0 |
實例賦值 | this.i = n | 1.2 |
方法調用 | Funct() | 5.9 |
新建對象 | New Object() | 980 |
新建數(shù)組 | New int[10] | 3100 |
從表1可以看出,新建一個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而若新建一個數(shù)組所花費的時間就更多了。
再看清除對象的過程。我們知道,Java語言的一個優(yōu)勢,就是Java程序員勿需再像C/C++程序員那樣,顯式地釋放對象,而由稱為垃圾收集器(Garbage Collector)的自動內存管理系統(tǒng),定時或在內存凸現(xiàn)出不足時,自動回收垃圾對象所占的內存。凡事有利總也有弊,這雖然為Java程序設計者提供了極大的方便,但同時它也帶來了較大的性能開銷。這種開銷包括兩方面,首先是對象管理開銷,GC為了能夠正確釋放對象,它必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請、引用、被引用、賦值等。其次,在GC開始回收“垃圾”對象時,系統(tǒng)會暫停應用程序的執(zhí)行,而獨自占用CPU。
因此,如果要改善應用程序的性能,一方面應盡量減少創(chuàng)建新對象的次數(shù);同時,還應盡量減少T1、T3的時間,而這些均可以通過對象池技術來實現(xiàn)。
對象池技術的基本原理
對象池技術基本原理的核心有兩點:緩存和共享,即對于那些被頻繁使用的對象,在使用完后,不立即將它們釋放,而是將它們緩存起來,以供后續(xù)的應用程序重復使用,從而減少創(chuàng)建對象和釋放對象的次數(shù),進而改善應用程序的性能。事實上,由于對象池技術將對象限制在一定的數(shù)量,也有效地減少了應用程序內存上的開銷。
實現(xiàn)一個對象池,一般會涉及到如下的類:
1)對象池工廠(ObjectPoolFactory)類
該類主要用于管理相同類型和設置的對象池(ObjectPool),它一般包含如下兩個方法:
·createPool:用于創(chuàng)建特定類型和設置的對象池;
·destroyPool:用于釋放指定的對象池;
同時為保證ObjectPoolFactory的單一實例,可以采用Singleton設計模式,見下述getInstance方法的實現(xiàn):
public static ObjectPoolFactory getInstance() { if (poolFactory == null) { poolFactory = new ObjectPoolFactory(); } return poolFactory; } |
2)參數(shù)對象(ParameterObject)類
該類主要用于封裝所創(chuàng)建對象池的一些屬性參數(shù),如池中可存放對象的數(shù)目的最大值(maxCount)、最小值(minCount)等。
3)對象池(ObjectPool)類
用于管理要被池化對象的借出和歸還,并通知PoolableObjectFactory完成相應的工作。它一般包含如下兩個方法:
·getObject:用于從池中借出對象;
·returnObject:將池化對象返回到池中,并通知所有處于等待狀態(tài)的線程;
4)池化對象工廠(PoolableObjectFactory)類
該類主要負責管理池化對象的生命周期,就簡單來說,一般包括對象的創(chuàng)建及銷毀。該類同ObjectPoolFactory一樣,也可將其實現(xiàn)為單實例。
通用對象池的實現(xiàn)
對象池的構造和管理可以按照多種方式實現(xiàn)。最靈活的方式是將池化對象的Class類型在對象池之外指定,即在ObjectPoolFactory類創(chuàng)建對象池時,動態(tài)指定該對象池所池化對象的Class類型,其實現(xiàn)代碼如下:
. . . public ObjectPool createPool(ParameterObject paraObj,Class clsType) { return new ObjectPool(paraObj, clsType); } . . . |
其中,paraObj參數(shù)用于指定對象池的特征屬性,clsType參數(shù)則指定了該對象池所存放對象的類型。對象池(ObjectPool)創(chuàng)建以后,下面就是利用它來管理對象了,具體實現(xiàn)如下:
public class ObjectPool { private ParameterObject paraObj;//該對象池的屬性參數(shù)對象 private Class clsType;//該對象池中所存放對象的類型 private int currentNum = 0; //該對象池當前已創(chuàng)建的對象數(shù)目 private Object currentObj;//該對象池當前可以借出的對象 private Vector pool;//用于存放對象的池 public ObjectPool(ParameterObject paraObj, Class clsType) { this.paraObj = paraObj; this.clsType = clsType; pool = new Vector(); } public Object getObject() { if (pool.size() <= paraObj.getMinCount()) { if (currentNum <= paraObj.getMaxCount()) { //如果當前池中無對象可用,而且已創(chuàng)建的對象數(shù)目小于所限制的最大值,就利用 //PoolObjectFactory創(chuàng)建一個新的對象 PoolableObjectFactory objFactory =PoolableObjectFactory.getInstance(); currentObj = objFactory.create Object (clsType); currentNum++; } else { //如果當前池中無對象可用,而且所創(chuàng)建的對象數(shù)目已達到所限制的最大值, //就只能等待其它線程返回對象到池中 synchronized (this) { try { wait(); } catch (InterruptedException e) { System.out.println(e.getMessage()); e.printStackTrace(); } currentObj = pool.firstElement(); } } } else { //如果當前池中有可用的對象,就直接從池中取出對象 currentObj = pool.firstElement(); } return currentObj; } public void returnObject(Object obj) { // 確保對象具有正確的類型 if (obj.isInstance(clsType)) { pool.addElement(obj); synchronized (this) { notifyAll(); } } else { throw new IllegalArgumentException("該對象池不能存放指定的對象類型"); } } } |
從上述代碼可以看出,ObjectPool利用一個java.util.Vector作為可擴展的對象池,并通過它的構造函數(shù)來指定池化對象的Class類型及對象池的一些屬性。在有對象返回到對象池時,它將檢查對象的類型是否正確。當對象池里不再有可用對象時,它或者等待已被使用的池化對象返回池中,或者創(chuàng)建一個新的對象實例。不過,新對象實例的創(chuàng)建并不在ObjectPool類中,而是由PoolableObjectFactory類的createObject方法來完成的,具體實現(xiàn)如下:
. . . public Object createObject(Class clsType) { Object obj = null; try { obj = clsType.newInstance(); } catch (Exception e) { e.printStackTrace(); } return obj; } . . . |
這樣,通用對象池的實現(xiàn)就算完成了,下面再看看客戶端(Client)如何來使用它,假定池化對象的Class類型為StringBuffer:
. . . //創(chuàng)建對象池工廠 ObjectPoolFactory poolFactory = ObjectPoolFactory. getInstance (); //定義所創(chuàng)建對象池的屬性 ParameterObject paraObj = new ParameterObject(2,1); //利用對象池工廠,創(chuàng)建一個存放StringBuffer類型對象的對象池 ObjectPool pool = poolFactory.createPool(paraObj,String Buffer.class); //從池中取出一個StringBuffer對象 StringBuffer buffer = (StringBuffer)pool.getObject(); //使用從池中取出的StringBuffer對象 buffer.append("hello"); System.out.println(buffer.toString()); . . . |
可以看出,通用對象池使用起來還是很方便的,不僅可以方便地避免頻繁創(chuàng)建對象的開銷,而且通用程度高。但遺憾的是,由于需要使用大量的類型定型(cast)操作,再加上一些對Vector類的同步操作,使得它在某些情況下對性能的改進非常有限,尤其對那些創(chuàng)建周期比較短的對象
專用對象池的實現(xiàn)
由于通用對象池的管理開銷比較大,某種程度上抵消了重用對象所帶來的大部分優(yōu)勢。為解決該問題,可以采用專用對象池的方法。即對象池所池化對象的Class類型不是動態(tài)指定的,而是預先就已指定。這樣,它在實現(xiàn)上也會較通用對象池簡單些,可以不要ObjectPoolFactory和PoolableObjectFactory類,而將它們的功能直接融合到ObjectPool類,具體如下(假定被池化對象的Class類型仍為StringBuffer,而用省略號表示的地方,表示代碼同通用對象池的實現(xiàn)):
public class ObjectPool { private ParameterObject paraObj;//該對象池的屬性參數(shù)對象 private int currentNum = 0; //該對象池當前已創(chuàng)建的對象數(shù)目 private StringBuffer currentObj;//該對象池當前可以借出的對象 private Vector pool;//用于存放對象的池 public ObjectPool(ParameterObject paraObj) { this.paraObj = paraObj; pool = new Vector(); } public StringBuffer getObject() { if (pool.size() <= paraObj.getMinCount()) { if (currentNum <= paraObj.getMaxCount()) { currentObj = new StringBuffer(); currentNum++; } . . . } return currentObj; } public void returnObject(Object obj) { // 確保對象具有正確的類型 if (StringBuffer.isInstance(obj)) { . . . } } |
結束語
恰當?shù)厥褂脤ο蟪丶夹g,能有效地改善應用程序的性能。目前,對象池技術已得到廣泛的應用,如對于網(wǎng)絡和數(shù)據(jù)庫連接這類重量級的對象,一般都會采用對象池技術。但在使用對象池技術時也要注意如下問題:
·并非任何情況下都適合采用對象池技術。基本上,只在重復生成某種對象的操作成為影響性能的關鍵因素的時候,才適合采用對象池技術。而如果進行池化所能帶來的性能提高并不重要的話,還是不采用對象池化技術為佳,以保持代碼的簡明。
·要根據(jù)具體情況正確選擇對象池的實現(xiàn)方式。如果是創(chuàng)建一個公用的對象池技術實現(xiàn)包,或需要在程序中動態(tài)指定所池化對象的Class類型時,才選擇通用對象池。而大部分情況下,采用專用對象池就可以了。