|
常用鏈接
留言簿(6)
隨筆分類(3)
隨筆檔案(19)
文章分類(77)
文章檔案(107)
相冊
JAVA
LIFE
TOOLS
WEB SERVER
WEB-JFRAME
最新隨筆
搜索
最新評論

閱讀排行榜
評論排行榜
Powered by: 博客園
模板提供:滬江博客
|
|
|
|
|
發新文章 |
|
|
集群配置(一):Tomcat集群配置
相關文檔請參見各個工具相應提供的文檔,工具的安裝此處不再介紹,默認地,將Apache安裝在$APACHE目錄,并將mod_jk-apache-
2.2.2.so改名為mod_jk.so放在$APACHE/ modules下(注意JK與Apache httpd的版本關系),兩個Tomcat
的安裝路徑都在$TOMCAT1和$TOMCAT2。 二、負載均衡 1、基于request的負載均衡 該
種方式下,負載均衡器 (load balancer)會根據各個node的狀況,把每個 http request進行分發。使用這樣的均衡策略,就必
須在多個節點之間復制用戶的session,實時保持整個集群的用 戶狀態同步,這種操作被稱為session復制
(session replication)。 該方法的優點是客戶不會被綁定都具體的node,只要還有一個node存活,用戶狀態都不會丟失,cluster都能夠繼續工作。缺點是隨著節點的增加,可能會因廣播風暴而導致性能大幅度下降 2、 基于session的負載均衡
該 種方式下,當用戶發出第一個request后,負載均衡器動態的把該用戶分配到某個節點,并記錄該節點的jvm路由,以后該用戶的所有request
都會被綁定這個jvm路由,用戶只會與該server發生交互,這種策略被稱為粘性session(session sticky)。該方法的優點是響應
速度快,多個節點之間無須通信。缺點也很明顯,某個node死掉以后,它負責的所有用戶都會丟失session。 3、Broker負載均衡
將節點進行分片,每個分片組成一個對外的服務整體,在片內使用基于request的負載均衡,而在片外使用基于session的負載均衡,使用這種處理將
使地Session復制的廣播保持為一個常量,不因為節點增加而導致性能下降,同時又保持高可靠性,不因為某個節點的崩潰而導致所有的Session數據
的丟失 這里將著重介紹第一和第二種負載均衡的配置 三、基于session的負載均衡 1)Apache配置,在$APACHE/conf/httpd.conf中增加如下配置:
以下內容為程序代碼:
# Load mod_jk module # Update this path to match your modules location LoadModule jk_module modules/mod_jk.so # Declare the module for <IfModule directive> (remove this line on Apache 2.x) #AddModule mod_jk.c # Where to find workers.properties # Update this path to match your conf directory location (put workers.properties next to httpd.conf) JkWorkersFile conf/workers.properties # Where to put jk logs # Update this path to match your logs directory location (put mod_jk.log next to access_log) JkLogFile jk/JK_LOG.txt # Set the jk log level [debug/error/info] JkLogLevel info # Select the log format JkLogStampFormat "[%a %b %d %H:%M:%S %Y] " # JkOptions indicate to send SSL KEY SIZE, JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories # JkRequestLogFormat set the request format JkRequestLogFormat "%w %V %T" # Send everything for context /examples to worker named worker1 (ajp13) JkMount /* loadbalancer
|
|
2)在$APACHE/conf/中創建workers.properties,內容如下,關于Worker的詳細配置,請參見Jarkarta-Tomcat的Connector文檔
以下內容為程序代碼:
worker.list=loadbalancer,server2,server1 # Define the first node... worker.server2.port=8010 worker.server2.host=localhost worker.server2.type=ajp13 worker.server2.lbfactor=1 worker.server2.local_worker=1 worker.server2.cachesize=10
# Define the first node... worker.server1.port=8009 worker.server1.host=localhost worker.server1.type=ajp13 worker.server1.lbfactor=1 worker.server1.local_worker=1 worker.server1.cachesize=10
# Now we define the load-balancing behaviour worker.loadbalancer.type=lb worker.loadbalancer.balanced_workers=server1,server2 worker.loadbalancer.sticky_session=1
|
|
在上面中,loadbalancer是一個虛擬Worker,并不代表任何節點,僅用于管理其他的Worker(注意其type為lb) 3)兩個Tomcat的配置 根據上面的workers.properties配置修改$TOMCAT/conf/server.xml的端口配置,并修改 <
Engine name="Catalina" defaultHost="localhost">為<Engine name=
"Catalina" defaultHost="localhost" jvmRoute="server1/2"> 啟動Tomcat、Apache,到功告成 四、基于request的負載均衡 在如上配置的基礎上增加如下配置: 1)workers.properties的worker.loadbalancer.sticky_session值改為0 2)server.xml中的Cluster配置注釋去掉(注意端口,如果在同臺機上進行負載均衡,注意端口不要沖突) 3)在應用的web.xml中增加<distributable/>
2006-7-26
WebWork+FreeMarker與JSF比較 WebWork
是一種傳統的MVC框架,其簡單而又不失強大,架構非常靈活、擴展性強,完成最核心的功能也僅通過實現Action接口,而擴展的功能基本上可以通過其攔
截器結構完成,另外,從WebWork2開始與ServletAPI的解耦也使其具備比較強的可測試性。然而其缺點也是非常地明顯,一方面其用戶群比較
少,缺少足夠文檔,另一方面,由于其開發團隊與Struts團隊合并以WebWork為核心開發新的MVC框架Struts Ti ,
WebWork2.2已經是其最終版本,缺乏后續版本的支持,而StrutsTi前途也還是個未知數。 JSF是JCP出品Sun力推的標
準,雖然出現較遲但可以說是來勢洶洶,大有想要一統MVC的架勢。JSF的優點在于其標準,附帶而來的好處是豐富的JSF組件可供選擇,其另一個賣點是拖
拽式的界面開發模式。然而,其JSF本身架構的復雜性足以讓人望而生畏,一方面,JSF組件的使用一旦出現問題非常難以調試,而其出錯信息往往沒有多少有
價值的東西,另一方面,一旦標準組件不能滿足需求需要獨立開發的話,難度非常高。 下面將對這兩種技術進行比較: 一、控制層: 1.耦合度 1)與ServletAPI的耦合 兩者都可以完全與ServletAPI的解耦,在開發時也都應該盡量避免與ServletAPI的耦合 2)與框架API的耦合 WebWork:所有的控制類必須實現Action接口 JSF:FacesBean不需要繼承任何JSF接口。但有時需要與界面元素(譬如列表框)產生耦合,開發時應該盡量將該部分隔離 評價:這兩種技術都是低耦合的,不相上下 2.IOC能力 WebWork本身提供了部分的IOC能力,但功能比較弱,已不被推薦。兩者都可以通過與Spring的結合獲 得IOC的能力,相對而言,JSF更簡單而且提供更多的功能。 評價:不相上下,JSF稍微勝出 3.AOP能力 WebWork:其攔截器功能本身就是一種AOP方式 JSF:可以通過與Spring的結合獲得AOP能力 評價:WebWork的AOP能力與WebWork的核心結合地更緊密,WebWork稍微勝出 4.配置 WebWork的配置本身不夠簡潔,而與Spring的結合更是出現重復的配置情況,相比而言,JSF更簡潔 評價:JSF勝出 5.可測試性 WebWork:WebWork本身的簡潔和靈活使其具備非常高的可測試性,可以脫離容器測試其控制層的行為 JSF:不清楚,似乎不能脫離容器獨立測試配置 評價:似乎是WebWork勝出 二、表現層: 1.組件化 FreeMarker:可以通過FreeMarker的宏能力將一些常用的組件,開發比較簡單,但同時,自己開發組件時間和質量上可能無法保障 JSF:JSF本身就是為組件而生,由于其標準化,有豐富的組件可以使用,在項目的前期投入少,不需要專門開發組件 評價:隨著項目的開展,組件化的優勢應該會越來越明顯。在這點上,JSF壓倒性勝出 2.可調試性 FreeMarker:FreeMarker本身的出錯定位能力非常強,往往出錯信息非常直觀,但一旦進行組件包裝,可調試性將大打折扣 JSF:JSF的組件本身比較復雜,調試性差,往往很難根據出錯信息定位出錯情況,更多的時候需要依靠經驗來解決 評價:FreeMarker稍勝出 3.擴展 FreeMarker:FreeMarker的宏功能非常簡單,功能擴展非常方便,但不足之處在于其邏輯表達能力比較差 JSF:JSF的組件的復雜性導致擴展新的功能是一個不小的代價,但Java語言強大的表達能力也使其能開發出復雜的功能 評價:FreeMarker稍勝出 4.表現邏輯能力 FreeMarker+WebWork:在WebWork的最新版本中提供了Ajax的客戶端驗證能力,但仍然比較原始 JSF:JSF有些客戶端驗證組件,但總的來說,仍然需要加強 評價:都比較差 5.Ajax FreeMarker+WebWork:WebWork在最新版本中提供了Ajax支持,但包裝暫時還是比較原始 JSF:有各種具備Ajax特性的界面組件存在,同時可使用AjaxAnywhere提供更好的用戶體驗 評價:JSF稍勝出
從如上的比較可以看出,WebWork+FreeMarker和JSF可以說各有勝場,我的看法是,WebWork+FreeMarker具有更平緩的學
習曲線,使用比較簡單,在應付小項目和小團隊上,略勝一籌;而面向大項目和大團隊,JSF的組件化在項目的進展中將發揮更大的作用。 除此之外,JSF的拖拽式的界面開發也是一個賣點,但我的看法是,對于一個熟手來說,其帶來的效率上的提高是微乎其微的,此外,一個提供這種功能的IDE,以下兩個問題是應該考慮的: 1)對第三方JSF組件的支持 2)自動化生成的代碼的維護
|
2006-7-24
在Hibernate3使用TreeCache
以下內容為程序代碼:
CREATE TESTUSER ( USERNAME VARCHAR(50) PRIMARY KEY, PASSWORD VARCHAR(50) NOT NULL ); INSERT INTO TESTUSER(USERNAME, PASSWORD) VALUES ('ayufox0', '11');
|
|
2.創建User類和hbm文件
以下內容為程序代碼:
package ayufox.hibernate.jbosscache;
import java.io.Serializable;
public class User implements Serializable { private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public boolean equals(Object obj) { if (obj instanceof User) { User u = (User) obj; return getUsername().equals(u.getUsername()); } else { return false; } } public int hashCode() { return getUsername().hashCode(); } }
|
|
以下內容為程序代碼:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="ayufox.hibernate.jbosscache"> <class name="User" table="testuser"> <!-- TreeCache只支持read-only和transactional --> <cache usage="transactional"/> <id name="username"> <generator class="assigned"/> </id> <property name="password" not-null="true"/> </class> </hibernate-mapping>
|
|
3.Hibernate配置文件(hibernate.cfg.xml)
以下內容為程序代碼:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">11</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.show_sql">true</property> <!-- 啟用TreeCache--> <property name="hibernate.cache.provider_class">org.hibernate.cache.TreeCacheProvider</property> <mapping resource="ayufox/hibernate/jbosscache/User.hbm.xml"/> </session-factory> </hibernate-configuration>
|
|
4.TreeCache配置(treecache.xml,與hibernate.cfg.xml),更詳細配置請參見JBoss TreeCache文檔
以下內容為程序代碼:
<?xml version="1.0" encoding="UTF-8"?> <server> <mbean code="org.jboss.cache.TreeCache" name="jboss.cache:service=TreeCache"> <depends>jboss:service=Naming</depends> <depends>jboss:service=TransactionManager</depends> <attribute name="TransactionManagerLookupClass">org.jboss.cache.DummyTransactionManagerLookup</attribute> <attribute name="CacheMode">REPL_SYNC</attribute> <attribute name="ClusterName">TreeCache-Cluster</attribute> <attribute name="ClusterConfig"> <config> <UDP mcast_addr="228.1.2.3" mcast_port="48866" ip_ttl="64" ip_mcast="true" mcast_send_buf_size="150000" mcast_recv_buf_size="80000" ucast_send_buf_size="150000" ucast_recv_buf_size="80000" loopback="false"/> <PING timeout="2000" num_initial_members="3" up_thread="false" down_thread="false"/> <MERGE2 min_interval="10000" max_interval="20000"/> <FD_SOCK/> <VERIFY_SUSPECT timeout="1500" up_thread="false" down_thread="false"/> <pbcast.NAKACK gc_lag="50" retransmit_timeout="600,1200,2400,4800" max_xmit_size="8192" up_thread="false" down_thread="false"/> <UNICAST timeout="600,1200,2400" window_size="100" min_threshold="10" down_thread="false"/> <pbcast.STABLE desired_avg_gossip="20000" up_thread="false" down_thread="false"/> <FRAG frag_size="8192" down_thread="false" up_thread="false"/> <pbcast.GMS join_timeout="5000" join_retry_timeout="2000" shun="true" print_local_addr="true"/> <pbcast.STATE_TRANSFER up_thread="true" down_thread="true"/> </config> </attribute> </mbean> </server>
|
|
5. 例子代碼:通過其輸出的SQL代碼來看工作的正常情況 進程1:隔一段時間修改密碼
以下內容為程序代碼:
package ayufox.hibernate.jbosscache;
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration;
public class Test1 { public static void main(String[] args) throws Exception { SessionFactory factory = new Configuration().configure() .buildSessionFactory(); int index = 0; while (true) { Session session = factory.openSession(); if (index % 5 == 0) { Transaction tx = session.beginTransaction(); User u = (User) session.load(User.class, "ayufox0"[img]/images/wink.gif[/img]; u.setPassword(new Long(System.currentTimeMillis()).toString()); session.update(u); tx.commit(); u = null; } else { User u = (User) session.load(User.class, "ayufox0"[img]/images/wink.gif[/img]; System.out.println("password:" + u.getPassword()); } index++; session.close(); Thread.sleep(5000); } } }
|
|
進程2:隔一段時間讀取密碼
以下內容為程序代碼:
package ayufox.hibernate.jbosscache;
import java.text.SimpleDateFormat; import java.util.Date;
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration;
public class Test2 { private final static SimpleDateFormat FORMAT = new SimpleDateFormat( "HH:mm:ss"[img]/images/wink.gif[/img];
public static void main(String[] args) throws Exception { SessionFactory factory = new Configuration().configure() .buildSessionFactory(); while (true) { Session session = factory.openSession(); User u = (User) session.load(User.class, "ayufox0"[img]/images/wink.gif[/img]; System.out.println("date:" + FORMAT.format(new Date(new Long(u.getPassword())))); u = null; session.close(); Thread.sleep(5000); } } }
2006-7-21
JBossCache-TreeCache體驗 TreeCache
是一種結構化的、基于復制的事務緩存。TreeCache是JBoss應用服務器中集群服務—包括JNDI集群、HTTP和EJB的Sesssion集
群、JMS集群—的基礎框架。其可以單獨使用,可以集成到JBossAS應用,也可以集成到其他的應用服務器上。TreeCache是一種樹狀結構,每個
節點擁有一個名字和多個或者沒有子節點,除跟節點沒有子節點其他節點有且只有一個父母節點,可以通過路徑名來訪問子節點(FQN:
Full Qualified Name),在一個TreeCache中可以存在多棵樹,,即可以有多個根節點。當應用于分布式環境時,由于
TreeCache是基于復制的,每個子節點的值必須是可序列化的。 在下面中,將通過例子來了解TreeCache的功能及其配置,使
用JBossCache1.4和JDK5.0。首先是一個最基本使用TreeCache的程序例子并配置一個TreeCache的配置骨架(各種常用的配
置可參見jboss-cache-dist-1.4.0.CR1版本的etc目錄,如下各種配置參考也可見該目錄下的范例配置,以下不再強調),見下:
以下內容為程序代碼:
TreeCache tree = new TreeCache(); tree.setClusterProperties("treecache.xml"[img]/images/wink.gif[/img]; tree.createService(); tree.startService(); tree.put("/a/b/c", "name", "Ben"[img]/images/wink.gif[/img]; tree.put("/a/b/c/d", "uid", new Integer(322649)); Integer tmp = (Integer) tree.get("/a/b/c/d", "uid"[img]/images/wink.gif[/img]; tree.remove("/a/b"[img]/images/wink.gif[/img]; tree.stopService(); tree.destroyService();
|
|
以下內容為程序代碼:
treecache.xml: <server> <mbean code="org.jboss.cache.TreeCache" name="jboss.cache:service=TreeCache"> <depends>jboss:service=Naming</depends> <depends>jboss:service=TransactionManager</depends> <attribute name="ClusterName">TreeCache-Cluster</attribute> <attribute name="ClusterConfig"> <config> <UDP mcast_addr="228.1.2.3" mcast_port="48866" ip_ttl="64" ip_mcast="true" mcast_send_buf_size="150000" mcast_recv_buf_size="80000" ucast_send_buf_size="150000" ucast_recv_buf_size="80000" loopback="false"/> <PING timeout="2000" num_initial_members="3" up_thread="false" down_thread="false"/> <MERGE2 min_interval="10000" max_interval="20000"/> <FD_SOCK/> <VERIFY_SUSPECT timeout="1500" up_thread="false" down_thread="false"/> <pbcast.NAKACK gc_lag="50" retransmit_timeout="600,1200,2400,4800" max_xmit_size="8192" up_thread="false" down_thread="false"/> <UNICAST timeout="600,1200,2400" window_size="100" min_threshold="10" down_thread="false"/> <pbcast.STABLE desired_avg_gossip="20000" up_thread="false" down_thread="false"/> <FRAG frag_size="8192" down_thread="false" up_thread="false"/> <pbcast.GMS join_timeout="5000" join_retry_timeout="2000" shun="true" print_local_addr="true"/> <pbcast.STATE_TRANSFER up_thread="true" down_thread="true"/> </config> </attribute> </mbean> </server>
|
|
其中ClusterConfig配置在前面JavaGroups的介紹詳細介紹,其它配置比較簡單,需要進一步了解請參見TreeCache文檔。 一、Cache分類 TreeCache
按功能分為三類:本地(Local)Cache、復制(Replication)Cache和失效(Invalidation)Cache。本地
Cache只應用于本地環境,后兩個Cache可應用于分布式環境,其中,在分布式環境中,復制Cache當一個Cache實例的一個節點值發生變化時會
將變化復制到其它實例中,而失效Cache是當一個Cache實例的一個節點值發生變化時會將其它實例的相應節點的值設為空,讓其重新去獲得該值,可通過
這種方式緩存大對象以減少在實例中復制對象的代價。分布式Cache(復制和失效Cache)又分為兩種,同步(REPL_ASYNC)和異步
(REPL_SYNC),同步Cache是在一個Cache實例做修改時,等待變化應用到其它實例后才返回,而異步Cache是在一個Cache實例做修
改時,即刻返回。其配置見下:
以下內容為程序代碼:
<!-- Valid modes are LOCAL REPL_ASYNC REPL_SYNC INVALIDATION_ASYNC INVALIDATION_SYNC --> <attribute name="CacheMode">REPL_SYNC</attribute>
|
|
二、事務和并行(Transaction And Concurrent) TreeCache
是一種事務Cache,與JDBC一樣,其包括兩方面內容:鎖和隔離級別。鎖分為悲觀鎖和樂觀鎖,當使用悲觀鎖時,分為五個隔離級別,分別是
SERIALIZABLE、REPEATABLE_READ (default)、READ_COMMITTED、READ_UNCOMMITTED和
NONE,隔離級別逐步減弱。樂觀鎖也叫版本鎖,其對數據進行操作時,將其復制到臨時區,操作之后將版本與原有數據比較,如果一致則將遞增版本并寫回,如
果不一致則回滾,由于樂觀鎖僅在復制出數據和提交數據時對數據加鎖,所以并行度更高,但如果寫操作比較頻繁地話則容易出現沖突導致回滾。
TreeCache默認使用悲觀鎖。使用TreeCache時,需要使用容器提供的事務管理器,一般使
JBossTransactionManagerLookup和GenericTransactionManagerLookup,前者應用于JBOSS
服務器,后者應用于其他服務器,也可使用DummyTransactionManagerLookup用于測試。如上介紹的配置如下:
以下內容為程序代碼:
<attribute name="NodeLockingScheme">OPTIMISTIC</attribute> <attribute name="IsolationLevel">REPEATABLE_READ</attribute> <attribute name="TransactionManagerLookupClass">org.jboss.cache.DummyTransactionManagerLookup</attribute>
|
|
三、逐出策略(Eviction Policy)
由于內存數量的局限,不可能將所有的Cache數據存放在內存中,但使用內存達到一定極限時,會將部分數據清除出內存,保存到其它持久媒質中,定義的什么
時候清除、如何清除的策略就是逐出策略。自定義一個逐出策略需要實現org.jboss.cache.eviction.EvictionPolicy、
org.jboss.cache.eviction.EvictionAlgorithm、 org.jboss.cache.eviction.EvictionQueue
和org.jboss.cache.eviction.EvictionConfiguration四個接口,系統提供了LRU
(Least recently used,最近最少使用)、LFU(Least Frequently Used最不經常使用)、FIFO
(First In First Out先進先出)、MRU(Most Recently Used最近最經常使用)四種實現,詳細參見
org.jboss.cache.eviction包的源代碼。配置如下:
以下內容為程序代碼:
<attribute name="EvictionPolicyConfig"> <config> <attribute name="wakeUpIntervalSeconds">5</attribute> <region name="/_default_"> <attribute name="maxNodes">5000</attribute> <attribute name="timeToLiveSeconds">1000</attribute> </region> <region name="/org/jboss/data" policyClass="org.jboss.cache.eviction.FIFOPolicy"> <attribute name="maxNodes">5000</attribute> </region> <region name="/test/" policyClass="org.jboss.cache.eviction.MRUPolicy"> <attribute name="maxNodes">10000</attribute> </region> <region name="/maxAgeTest/"> <attribute name="maxNodes">10000</attribute> <attribute name="timeToLiveSeconds">8</attribute> <attribute name="maxAgeSeconds">10</attribute> </region> </config> </attribute>
|
|
也可以多對所有的區域定義同樣的逐出策略
以下內容為程序代碼:
<attribute name="EvictionPolicyClass">org.jboss.cache.eviction.LFUPolicy</attribute>
|
|
四、Cache加載 由于逐出策略的存在,那么當我們重新需要獲得一個原來在緩存中但確由內存原因被逐出的數據時,就需要定義一種加載策略,使地可以重新找回數據,同時,Cache加載也肩負在將數據逐出時將數據保存到持久媒質的責任。 根據將數據保存媒質的不同,Cache加載包括FileCacheLoader、JDBCCacheLoader等等,可以同時使用多種加載器來靈活定制加載策略。例見下:
以下內容為程序代碼:
<attribute name="CacheLoaderConfiguration"> <config> <passivation>false</passivation> <preload>/</preload> <shared>true</shared> <cacheloader> <class>org.jboss.cache.loader.ClusteredCacheLoader</class> <properties> timeout=1000 </properties> <async>true</async> <fetchPersistentState>false</fetchPersistentState> ignoreModifications>false</ignoreModifications> <purgeOnStartup>false</purgeOnStartup> </cacheloader> <cacheloader> <class>org.jboss.cache.loader.JDBCCacheLoader</class> <properties> cache.jdbc.table.name=jbosscache cache.jdbc.table.create=true cache.jdbc.table.drop=true cache.jdbc.table.primarykey=jbosscache_pk cache.jdbc.fqn.column=fqn cache.jdbc.fqn.type=varchar(255) cache.jdbc.node.column=node cache.jdbc.node.type=longblob cache.jdbc.parent.column=parent cache.jdbc.driver=com.mysql.jdbc.Driver cache.jdbc.url=jdbc:mysql://localhost:3306/jbossdb cache.jdbc.user=root cache.jdbc.password= </properties> <async>true</async> <fetchPersistentState>false</fetchPersistentState> <purgeOnStartup>false</purgeOnStartup> </cacheloader> </config> </attribute>
|
|
我們將通過定制如上的配置信息以更有效地使用JBossCache。詳細情況可參考JBoss TreeCache參考文檔和范例。
2006-7-19
JavaGroups—構建分布式通信的基礎(下)
以下內容為程序代碼:
public interface MessageListener { void receive(Message msg); byte[] getState(); void setState(byte[] state); }
|
|
2)ExtendedMessageListener:擴展消息監聽器
以下內容為程序代碼:
public interface ExtendedMessageListener extends MessageListener { byte[] getState(String state_id); void setState(String state_id, byte[] state); }
|
|
3)MemberShipListener:成員監聽器
以下內容為程序代碼:
public interface MembershipListener { void viewAccepted(View new_view); void suspect(Object suspected_mbr); void block(); }
|
|
4)ChannelListener:通道監聽器
以下內容為程序代碼:
public interface ChannelListener { void channelConnected(Channel channel); void channelDisconnected(Channel channel); void channelClosed(Channel channel); void channelShunned(); void channelReconnected(Address addr); }
|
|
5)Receiver:接受者
以下內容為程序代碼:
public interface Receiver extends MessageListener, MembershipListener { }
|
|
6)ExtendedReceiver:擴展接受者
以下內容為程序代碼:
public interface ExtendedReceiver extends ExtendedMessageListener, MembershipListener { }
|
|
示例如下:
以下內容為程序代碼:
JChannel ch=new JChannel(); ch.setReceiver(new ExtendedReceiverAdapter() { public void receive(Message msg) { System.out.println("received message " + msg); } public void viewAccepted(View new_view) { System.out.println("received view " + new_view); } }); ch.connect("bla"[img]/images/wink.gif[/img];
|
|
在通道同時擔當接受者和發送者時需要創建至少兩個線程分擔兩個不同的工作,通過向Jchannel注冊一個接受者(Receiver),使我們不需要自己顯式地創建接受線程 二、高層組件:以下類位于org.jgroups.blocks包下 使用JavaGroups的基本接口和通道編程比較直接而直觀,缺點是編程有時過于繁雜,JavaGroups為我們提供了一些高層的組件降低編程的難度。(以下類位于org.jgroups.blocks包下) 1.拉推模式適配器(PullPushAdapter) 一般直接使用Jchannel,是采用拉的方式,即用戶自己編程去“推動”接收和發送信息,通過該適配器,即轉換為由用戶注冊一個Listener,由適配器去“推動“接收和發送信息,例:
以下內容為程序代碼:
public class PullPushTest implements MessageListener { Channel channel; PullPushAdapter adapter; byte[] data="Hello world".getBytes(); public void receive(Message msg) { System.out.println("Received msg: " + msg); } public void start() throws Exception { channel=new JChannel(); channel.connect("PullPushTest"[img]/images/wink.gif[/img]; adapter=new PullPushAdapter(channel); adapter.setListener(this); for(int i=0; i < 10; i++) { System.out.println("Sending msg #" + i); channel.send(new Message(null, null, data)); Thread.currentThread().sleep(1000); } adapter.stop(); channel.close(); } public static void main(String args[]) { try { new PullPushTest().start(); } catch(Exception e) { /* error */ } } }
|
|
2.信息分發器(MessageDispatcher) Jchannel是采用異步的方式接受信息的,如果想使用同步接受信息,可使用該類,當然,該類也可用于異步接受信息,由于該類內部使用了PullPushAdapter,所以其也是“推“模式。例見下:
以下內容為程序代碼:
public class MessageDispatcherTest implements RequestHandler { Channel channel; MessageDispatcher disp; RspList rsp_list; public void start() throws Exception { channel=new JChannel(); disp=new MessageDispatcher(channel, null, null, this); channel.connect("MessageDispatcherTestGroup"[img]/images/wink.gif[/img]; for(int i=0; i < 10; i++) { Thread.sleep(100); System.out.println("Casting message #" + i); rsp_list=disp.castMessage(null, new Message(null, null, new String("Number #" + i)), GroupRequest.GET_ALL, 0);//GET_ALL表示等待所有信息發完 System.out.println("Responses:\n" +rsp_list); } channel.close(); disp.stop(); } public Object handle(Message msg) { System.out.println("handle(): " + msg); return new String("Success !"[img]/images/wink.gif[/img]; } public static void main(String[] args) { try { new MessageDispatcherTest().start(); } catch(Exception e) { System.err.println(e); } } }
|
|
看看每次操作的返回結果RspList類的聲明
以下內容為程序代碼:
public class RspList { public boolean isReceived(Address sender); public int numSuspectedMembers(); public Vector getResults(); public Vector getSuspectedMembers(); public boolean isSuspected(Address sender); public Object get(Address sender); public int size(); public Object elementAt(int i) throws ArrayIndexOutOfBoundsException; } 其保存了每條信息的操作結果情況 3.RPC分發器(RpcDispatcher) RPC用戶遠程與本地相同的對象的方法調用,見下面例子: [code] public class RpcDispatcherTest { Channel channel; RpcDispatcher disp; RspList rsp_list; public int print(int number) throws Exception { return number * 2; } public void start() throws Exception { channel=new JChannel(props); disp=new RpcDispatcher(channel, null, null, this); channel.connect("RpcDispatcherTestGroup"[img]/images/wink.gif[/img]; for(int i=0; i < 10; i++) { Thread.sleep(100); rsp_list=disp.callRemoteMethods(null, "print", new Integer(i), GroupRequest.GET_ALL, 0); System.out.println("Responses: " +rsp_list); } channel.close(); disp.stop(); } public static void main(String[] args) { try { new RpcDispatcherTest().start(); } catch(Exception e) { System.err.println(e); } } }
|
|
三、分布式數據結構: 使用分布式數據結構可以在多個進程的內部保持數據結構的多個實例,在任何時刻每個實例都有相同的值,主要有DistributedHashtable、ReplicatedHashtable和DistributedTree,其使用比較直接,此處不再做詳細舉例
參考:《JGroups文檔》 |
|
2006-7-17
JavaGroups—構建分布式通信的基礎(上) JavaGroups
是一種可靠組通信工具,在同一個臺主機、局域網甚至是廣域網中,組成員可以加入一個組,發送消息給其它的組成員并從其它成員中接收消息,系統跟蹤所有組成
員加入、退出和崩潰,并將這些系統信息發送給其它組成員。在JavaGroups中,組并不需要明確地創建,當第一個成員加入一個組時,自動創建了該組,
第一個成員同時作為系統的協調者統一發送系統信息(譬如成員的加入退出等)給其它成員,而其他組成員通過與系統協調者的通信來獲得系統的變化情況。目前大
部分開源的分布式緩存的底層都是基于JGroups,包括鼎鼎大名的JBossCache、OSCache等等。

上圖為JavaGroups的概念架構圖。可以看出,JavaGroups從概念上自下而上分為三個部分,協議棧、通道(Channel接口)、
BuildingBlocks And DistributedDataStructures (高層組件和分布式數據結構)和應用程序。其中,通道、高
層組件和分布式數據結構是系統暴露出來的接口,用戶應用程序通過與該部分的交互來使用底層的通信功能。以下將分別對各部分進行詳細介紹。
協議棧是系統最核心的部分,其定義了組成員之間如何通信、協調,組如何初始化,組如何檢測成員等等最核心和根本的功能。協議棧是一些有順序的協議的組合,
當通道發送一條消息時,首先將其傳遞給協議棧(Protocol stack),協議棧將其傳遞給最頂端的協議處理,每個協議處理完之后將其傳遞給其底端
協議處理,最后,最底端的協議將消息通過網絡將其傳送出去。 下圖是一個協議堆例子,其中:CAUSAL協議定義了一個組成員順序發送出去
的消息必須按相同的順序被其它的組成員所接受;GMS協議定義了組管理服務,包括成員的加入、離開和崩潰的處理;MERGE協議定義了子組的合并,即協調
者定期廣播一些HELLO和地址信息,如果接受到回應和其組名一致并且目前還沒加入本組,則將其合并進來;FRAG定義了消息分楨機制,即當一個消息超過
一定大小的時候將其分解為幾個小消息發送;UDP定義了底層的傳送機制。協議棧靈活的定義機制使用戶可以靈活地根據自己的需求來定義使用的協議棧。
 下面列出一些常用的協議的作用: 1)核心微協議 CAUSAL:組中消息的原因排序。實現使用一個矢量時鐘 FD:使用 heartbeat 協議的故障檢測。根據成員關系列表中的排序,Heartbeat 消息被發送到鄰居成員 FD_SOCK:基于 TCP 套接字的故障檢測。基于環 的 ping 被在鄰居成員之間發送。當所有成員都在同一物理主機上時工作得最好 FD_PID: 使用進程 ID 的故障檢測 (本地 JNI 代碼,以獲得所需的 PID)。只能在同一臺主機上 (一個 IP 地址) 工作 FD_PROB: 使用隨機算法的故障檢測。組的每個成員發送 heartbeat,并維護其他成員的 heartbeat 計數 FLOW_CONTROL:流控制實現,限制了消息收據之間發送的消息的最大數量 FLUSH:以一致的方式跨所有成員清除所有的消息。通常是在視圖更改之前執行 FRAG:消息分段和重新裝配(Message fragmentation and reassembly)。確保較大的消息在沿著棧往下發送之前被分為 FRAG_SIZE:大小的段。分段的消息在沿著棧往上發送之前在接收方被重新裝配 GMS:組管理服務。基于虛擬同步模型管理組成員關系 MERGEMERGE2:合并分離的子組。當網絡由于故障而分離成多個部分時就形成了子組 NACKACK:實現可靠的傳輸。基于消息序列號請求丟失消息的重新傳輸。確保從每個起始通道發出的消息的正確排序JMS將 Java Message Service 用于傳輸。與任何 JMS 實現協同工作 STATE_TRANSFER: 實現狀態傳輸協議,使得新成員可以從同等物(coordinator)或所有成員獲得現有的狀態。需要 FLUSH 微協議在協議棧上 UNICAST:實現可靠的單播傳輸。請求丟失消息的重新傳輸,并確保發出消息的正確排序 VIEW_ENFORCER:直到接收到第一個 VIEW_CHANGE: 才丟棄消息。客戶端只有在成為組成員后才需要處理消息 STABLE:實現分布式的垃圾收集協議 (也就是說,刪除所有已被所有組成員接收到的消息) VERIFY_SUSPECT:發送消息以確保以前懷疑的成員已真正崩潰(crashed) UDP:一般用作組消息傳輸的最低層。IP 多播用于組廣播,而 UDP 用于點到點通信PING:用于引導成員關系管理。使用 IP 多播 "ping" 消息來定位成員,然后再請求這 些成員加入組中 2)基于隨機廣播的微協議 pbcast.GMS:組管理服務,基于隨機廣播 (雜談)。不需要 FLUSH pbcast.FD:基于雜談(gossip) 的被動故障檢測。不發送 heartbeat 消息 pbcast.PBCAST:實現隨機廣播,有規律地以雜談的方式發送到成員關系的一個隨機子集 pbcast.STABLE:實現分布式的垃圾收集協議 (也就是說,刪除所有已被所有組成員接收到的消息) pbcast.NAKACK:對丟失消息的重新傳輸和消息的順序傳送的否認實現pbcast.STATE_TRANSFER:將隨機廣播用于狀態傳輸實現。不需要 QUEUE 3)穿越 WAN 和防火墻的微協議 TCP :用于取代 UDP 作為最低層傳輸。通過 TCP 連接發送多個單播消息到成員,而不是發送多播消息 (不可能)。已經內置了可靠性、FIFO 排序和流控制 TCPPING:使用一組已知的成員來引導通過 TCP 的成員關系管理 TCPGOSSIP:使用一個外部雜談 (參見 參考資料) 服務器,來為成員關系管理的引導定位成員的初始集合 TUNNEL: 當用于代替 UDP 或 TCP 作為最低層傳輸協議時,啟用通過防火墻的隧道技術。與防火墻外的一個 JavaGroups Router 進程協同工作 JavaGroups中所有的協議都在org.jgroups.protocol包及其自包中定義,各協議詳細的定義和實現可參見源代碼。
通道類似于Socket,用戶通過通道與底層的交互,使用編程更加方便。通道相當于一個組的句柄,對用戶而言,組通信最核心的功能是加入組、退出組、發送
消息、接收消息,都通過通道來達到這個目的,即org.jgroups.Channel的實現。值得注意的是,JavaGroups中Channel僅是
一個抽象類,可以使用不同的通道實現,見下圖:
 在這里我們僅探討最常用的org.jgroups.Jchannel實現。 1)指定協議棧并創建通道,并連接到一個組
以下內容為程序代碼:
String props = "UDP(mcast_addr=228.8.8.8;mcast_port=45566;ip_ttl=32;" + "mcast_send_buf_size=64000;mcast_recv_buf_size=64000):" + "PING(timeout=2000;num_initial_members=3):" + "MERGE2(min_interval=5000;max_interval=10000):" + "FD_SOCK:" + "VERIFY_SUSPECT(timeout=1500):" + "pbcast.NAKACK(max_xmit_size=8096;gc_lag=50;retransmit_timeout=600,1200,2400,4800):" + "UNICAST(timeout=600,1200,2400,4800):" + "pbcast.STABLE(desired_avg_gossip=20000):" + "FRAG(frag_size=8096;down_thread=false;up_thread=false):" + "pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;" + "shun=false;print_local_addr=true):" + "STATE_TRANSFER:QUEUE"; JChannel channel = new JChannel(props); channel.connect(“groupname”);
|
|
2)發送一條消息
以下內容為程序代碼:
channel.send(new Message(null, null, “Hello, JavaGroups”);
|
|
3)接受一條消息
以下內容為程序代碼:
Object obj = channel.receive(0);
|
|
4)退出組
以下內容為程序代碼:
channel.close();
|
|
可以通過設置通道的一些選項來定義通道的行為 public void setOpt(int option, Object value) 目前有效的值分別是 1)Channel.BLOCK:是否接受阻塞信息,默認為Boolean.TRUE 2)Channel.LOCAL:消息廣播時,消息發送者是否也會接受到該消息,默認為Boolean.TRUE 3)Channel.AUTO_RECONNECT:通道斷開時是否自動重連接,默認值為Boolean.TRUE 4)Channel.AUTO_GETSTATE:是否自動重新獲得狀態信息,默認值為Boolean.TRUE 系統以及組成員之間通過消息的方式來進行通信,消息分為如下幾種(以下說明的類位于org.jgroups包下): 1)普通消息(Message類) 用戶組成員之間的交互消息,包括消息頭、消息接受者、消息發送者和消息體 2)視圖(View類) 即組成員的情況,當一個成員加入、退出或者崩潰時,系統協調者(見上,第一個組成員)向其它發送新的視圖信息 3)疑似事件(SuspectEvent類) 當檢測到一個組成員可能已經離開或者崩潰時,發送該信息。 4)阻塞事件(BlockEvent類) 當系統協調者在更新視圖時,會發送該阻塞信息給其它組成員,讓其在視圖更新之后再做發送消息的操作 5)獲得狀態事件(GetStateEvent類) 6)設置狀態事件(SetStateEvent類) 應用程序可能需要保持一些狀態,并在各個成員之間同步,可以通過這兩個事件知曉狀態的變化 就此,我們已經對JavaGroups的架構和組件有一個基本的了解,下部分將通過例子進一步介紹JavaGroups的使用
|
2006-7-14
對象的強、軟、弱和虛引用 (摘)
在JDK1.2以前的版本中,當一個對象不被任何變量引用,那么程序就無法再使用這個對象。也就是說,只有對象處于可觸及狀態,程序才能使用它。這就像在
日常生活中,從商店購買了某樣物品后,如果有用,就一直保留它,否則就把它扔到垃圾箱,由清潔工人收走。一般說來,如果物品已經被扔到垃圾箱,想再把它撿
回來使用就不可能了。 但有時候情況并不這么簡單,你可能會遇到類似雞肋一樣的物品,食之無味,棄之可惜。這種物品現在已經無用了,保留它會占空
間,但是立刻扔掉它也不劃算,因為也許將來還會派用場。對于這樣的可有可無的物品,一種折衷的處理辦法是:如果家里空間足夠,就先把它保留在家里,如果家
里空間不夠,即使把家里所有的垃圾清除,還是無法容納那些必不可少的生活用品,那么再扔掉這些可有可無的物品。 從JDK1.2版本開始,把對象的引用分為四種級別,從而使程序能更加靈活的控制對象的生命周期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。 1.強引用
本章前文介紹的引用實際上都是強引用,這是使用最普遍的引用。如果一個對象具有強引用,那就類似于必不可少的生活用品,垃圾回收器絕不會回收它。當內存空
間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。 2.軟引用(SoftReference) 如果一個對象只具有軟引用,那就類似于可有可物的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。 軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。 3.弱引用(WeakReference)
如果一個對象只具有弱引用,那就類似于可有可物的生活用品。弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它
所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,
因此不一定會很快發現那些只具有弱引用的對象。 弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。 4.虛引用(PhantomReference) "虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。 虛
引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在于:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾
回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否
已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取
必要的行動。 在本書中,"引用"既可以作為動詞,也可以作為名詞,讀者應該根據上下文來區分"引用"的含義。 在java.lang.ref
包中提供了三個類:SoftReference類、WeakReference類和PhantomReference類,它們分別代表軟引用、弱引用和虛
引用。ReferenceQueue類表示引用隊列,它可以和這三種引用類聯合使用,以便跟蹤Java虛擬機回收所引用的對象的活動。以下程序創建了一個
String對象、ReferenceQueue對象和WeakReference對象:
//創建一個強引用 String str = new String("hello" ;
//創建引用隊列, <String>為范型標記,表明隊列中存放String對象的引用 ReferenceQueue<String> rq = new ReferenceQueue<String>();
//創建一個弱引用,它引用"hello"對象,并且與rq引用隊列關聯 //<String>為范型標記,表明WeakReference會弱引用String對象 WeakReference<String> wf = new WeakReference<String>(str, rq);
以上程序代碼執行完畢,內存中引用與對象的關系如下圖所示。

在圖11-10中,帶實線的箭頭表示強引用,帶虛線的箭頭表示弱引用。從圖中可以看出,此時"hello"對象被str強引用,并且被一個WeakReference對象弱引用,因此"hello"對象不會被垃圾回收。 在以下程序代碼中,把引用"hello"對象的str變量置為null,然后再通過WeakReference弱引用的get()方法獲得"hello"對象的引用:
String str = new String("hello" ; //① ReferenceQueue<String> rq = new ReferenceQueue<String>(); //② WeakReference<String> wf = new WeakReference<String>(str, rq); //③
str=null; //④取消"hello"對象的強引用 String str1=wf.get(); //⑤假如"hello"對象沒有被回收,str1引用"hello"對象
//假如"hello"對象沒有被回收,rq.poll()返回null Reference<? extends String> ref=rq.poll(); //⑥
執行完以上第④行后,內存中引用與對象的關系如下圖所示,此時"hello"對象僅僅具有弱引用,因此它有可能被垃圾回收。假如它還沒有被垃圾回收,那么
接下來在第⑤行執行wf.get()方法會返回 "hello"對象的引用,并且使得這個對象被str1強引用。再接下來在第⑥行執行rq.poll()
方法會返回null,因為此時引用隊列中沒有任何引用。ReferenceQueue的poll()方法用于返回隊列中的引用,如果沒有則返回null。

在以下程序代碼中,執行完第④行后,"hello"對象僅僅具有弱引用。接下來兩次調用System.gc()方法,催促垃圾回收器工作,從而提高
"hello"對象被回收的可能性。假如"hello"對象被回收,那么WeakReference對象的引用被加入到 ReferenceQueue
中,接下來wf.get()方法返回null,并且rq.poll()方法返回WeakReference對象的引用。下圖 顯示了執行完第⑧行后內存中
引用與對象的關系。
String str = new String("hello" ; //① ReferenceQueue<String> rq = new ReferenceQueue<String>(); //② WeakReference<String> wf = new WeakReference<String>(str, rq); //③ str=null; //④
//兩次催促垃圾回收器工作,提高"hello"對象被回收的可能性 System.gc(); //⑤ System.gc(); //⑥ String str1=wf.get(); //⑦ 假如"hello"對象被回收,str1為null Reference<? extends String> ref=rq.poll(); //⑧

在
以下例程11-15的References類中,依次創建了10個軟引用、10個弱引用和10個虛引用,它們各自引用一個Grocery對象。從程序運行
時的打印結果可以看出,虛引用形同虛設,它所引用的對象隨時可能被垃圾回收,具有弱引用的對象擁有稍微長的生命周期,當垃圾回收器執行回收操作時,有可能
被垃圾回收,具有軟引用的對象擁有較長的生命周期,但在Java虛擬機認為內存不足的情況下,也會被垃圾回收。
例程11-15 References.java import java.lang.ref.*; import java.util.*;
class Grocery{ private static final int SIZE = 10000; //屬性d使得每個Grocery對象占用較多內存,有80K左右 private double[] d = new double[SIZE]; private String id; public Grocery(String id) { this.id = id; } public String toString() { return id; } public void finalize() { System.out.println("Finalizing " + id); } }
public class References { private static ReferenceQueue<Grocery> rq = new ReferenceQueue<Grocery>(); public static void checkQueue() { Reference<? extends Grocery> inq = rq.poll(); //從隊列中取出一個引用 if(inq != null) System.out.println("In queue: "+inq+" : "+inq.get()); }
public static void main(String[] args) { final int size=10;
//創建10個Grocery對象以及10個軟引用 Set<SoftReference<Grocery>> sa = new HashSet<SoftReference<Grocery>>(); for(int i = 0; i < size; i++) { SoftReference<Grocery> ref= new SoftReference<Grocery>(new Grocery("Soft " + i), rq); System.out.println("Just created: " +ref.get()); sa.add(ref); } System.gc(); checkQueue();
//創建10個Grocery對象以及10個弱引用 Set<WeakReference<Grocery>> wa = new HashSet<WeakReference<Grocery>>(); for(int i = 0; i < size; i++) { WeakReference<Grocery> ref= new WeakReference<Grocery>(new Grocery("Weak " + i), rq); System.out.println("Just created: " +ref.get()); wa.add(ref); } System.gc(); checkQueue();
//創建10個Grocery對象以及10個虛引用 Set<PhantomReference<Grocery>> pa = new HashSet<PhantomReference<Grocery>>(); for(int i = 0; i < size; i++) { PhantomReference<Grocery>ref = new PhantomReference<Grocery>(new Grocery("Phantom " + i), rq); System.out.println("Just created: " +ref.get()); pa.add(ref); } System.gc(); checkQueue(); } }
在Java集合中有一種特殊的Map類型:WeakHashMap,在這種Map中存放了鍵對象的弱引用,當一個鍵對象被垃圾回收,那么相應的值對象的引
用會從Map中刪除。WeakHashMap能夠節約存儲空間,可用來緩存那些非必須存在的數據。關于Map接口的一般用法,可參見本書第15章的
15.4節(Map)。 以下例程11-16的MapCache類的main()方法創建了一個WeakHashMap對象,它存放了一組Key對象的弱引用,此外main()方法還創建了一個數組對象,它存放了部分Key對象的強引用。
例程11-16 MapCache.java import java.util.*; import java.lang.ref.*;
class Key { String id; public Key(String id) { this.id = id; } public String toString() { return id; } public int hashCode() { return id.hashCode(); } public boolean equals(Object r) { return (r instanceof Key) && id.equals(((Key)r).id); } public void finalize() { System.out.println("Finalizing Key "+ id); } }
class Value { String id; public Value(String id) { this.id = id; } public String toString() { return id; } public void finalize() { System.out.println("Finalizing Value "+id); } }
public class MapCache { public static void main(String[] args) throws Exception{ int size = 1000; // 或者從命令行獲得size的大小 if(args.length > 0)size = Integer.parseInt(args[0]);
Key[] keys = new Key[size]; //存放鍵對象的強引用 WeakHashMap<Key,Value> whm = new WeakHashMap<Key,Value>(); for(int i = 0; i < size; i++) { Key k = new Key(Integer.toString(i)); Value v = new Value(Integer.toString(i)); if(i % 3 == 0) keys[i] = k; //使Key對象持有強引用 whm.put(k, v); //使Key對象持有弱引用 } //催促垃圾回收器工作 System.gc();
//把CPU讓給垃圾回收器線程 Thread.sleep(8000); } }
以上程序的部分打印結果如下:
Finalizing Key 998 Finalizing Key 997 Finalizing Key 995 Finalizing Key 994 Finalizing Key 992 Finalizing Key 991 Finalizing Key 989 Finalizing Key 988 Finalizing Key 986 Finalizing Key 985 Finalizing Key 983
從打印結果可以看出,當執行System.gc()方法后,垃圾回收器只會回收那些僅僅持有弱引用的Key對象。id可以被3整數的Key對象持有強引用,因此不會被回收。 |
Java調試平臺 Java Platform Debugger Architecture
(JPDA:Java平臺調試架構) 由Java虛擬機后端和調試平臺前端組成,Java虛擬機提供了Java調試的功能,而調試平臺通過調試交互協議向
Java虛擬機請求服務以對在虛擬機中運行的程序進行調試(見下圖)。
 JPDA
通過兩個接口和協議來完成如上的說明,分別是JVMTI(Java虛擬機工具接口)、JDWP(Java調試連線協議)和JDI(Java調試接口)。
JVMTI定義了虛擬機應該提供的調試服務,包括調試信息(Information譬如棧信息)、調試行為(Action譬如客戶端設置一個斷點)和通知
(Notification譬如到達某個斷點時通知客戶端),該接口由虛擬機實現者提供實現,并結合在虛擬機中;JDWP定義調試服務和調試器之間的通
信,包括定義調試信息格式和調試請求機制;而JDI在語言的高層次上定義了調試者可以使用的調試接口以能方便地與遠程的調試服務進行交互,Java語言實
現,調試器實現者可直接使用該接口訪問虛擬機調試服務。 當虛擬機的調試服務運行時,虛擬機作為調試的服務提供端,監聽一個連接,而調試
器通過該連接與虛擬機進行交互。目前,Windows平臺的JVM提供了兩種方式的連接:共享內存和 Socket連接,共享內存的服務提供端和調試端只
能位于同一臺機,而Socket連接則支持不同異機調試,即遠程調試。 在前面的篇章中,利用JPDA構建調試平臺([a] http://www.blogcn.com/u2/38/21/ayufox/blog/36956440.html [/a])介紹了如何使用Eclipse構建測試平臺,在本篇將詳細介紹Sun HotSpot虛擬機提供啟用詳細參數設置。 1.啟用調試服務 -Xdebug 啟用調試 -Xrunjdwp:<sub-options> 加載JVM的JPDA參考實現庫 2.Xrunjdwp子參數(sub-options)配置 Xrunjdwp子參數的配置格式如下 -Xrunjdwp:<name1>[=<value1>],<name2>[=<value2>]... 可以指定如下子參數 help 打印幫助信息并結束虛擬機運行 transport 連接方式,有dt_socket和dt_shmem兩種,分別是Socket和共享內存 server 是否以服務器方式啟,默認為n,即虛擬機作為調試客戶端,一般將其指為y值,同時指定address參數,則虛擬機會監聽到該address的調試請求,如果不指定address,則虛擬機使用address的默認值 address 連接地址,當指定server=n時,虛擬機在該地址上啟動調試器,如果server=y,虛擬機監聽該地址。如果使用socket連接,則address的默認值是一個隨機端口 launch 與onthrow 和/或 onuncaught(見下)結合使用,當某個特定事件發生時,啟動一個調試器進程進行實時調試 onthrow 當某個特定的異常拋出時才加載jdwp包,也就是當異常拋出時才啟動調試服務 onuncaught 默認值為n,如果設為y,則當運行異常(RuntimeException)拋出時才加載jdwp包,即才啟動調試服務 stdalloc 默認的,jdwp實現隨機使用一個內存分配器來分配內存,如果指定值為y,則使用C運行庫內存分配器分配內存 suspend 是否掛起,默認值為y,即主程序并不馬上運行直到有調試請求到達,一般指定為n 3.例: -Xrunjdwp:transport=dt_socket,server=y,address=8000 在8000端口監聽Socket連接,掛起VM并且不加載運行主函數直到調試請求到達 -Xrunjdwp:transport=dt_shmem,server=y,suspend=n 選擇一個可用的共享內存(因為沒有指address)并監聽該內存連接,同時加載運行主函數 -Xrunjdwp:transport=dt_socket,address=myhost:8000 連接到myhost:8000提供的調試服務(server=n,以調試客戶端存在),掛起VM并且不加載運行主函數 -Xrunjdwp:transport=dt_shmem,address=mysharedmemory 通過共享內存的方式連接到調試服務,掛起VM并且不加載運行主函數 -Xrunjdwp:transport=dt_socket,server=y,address=8000,onthrow=java.io.IOException,launch=/usr/local/bin/debugstub 等待java.io.IOException被拋出,然后掛起VM并監聽8000端口連接,在接到調試請求后以命令/usr/local/bin/debugstub dt_socket myhost:8000執行 -Xrunjdwp:transport=dt_shmem,server=y,onuncaught=y,launch=d:\bin\debugstub.exe
等待一個RuntimeException被拋出,然后掛起VM并監聽一個可用的共享內存,在接到調試請求后以命令d:\bin\
debugstub.exe dt_shmem <address>執行,<address>是可用的共享內存
參考:JDK參考文檔
ayufox 發表于 >2006-7-12 12:47:20 [全文] [評論] [引用] [推薦] [檔案] [推給好友] [收藏到網摘]
|
Sun HotSpot VM 垃圾回收調優 Java
虛擬機的一個強大之處在于其提供垃圾自動回收,對開發人員掩蓋了內存分配和回收的細節,然而,在某些大型應用中,當垃圾回收(GC)本身成為瓶頸,我們將
不可避免需要深入了解虛擬機掩蓋之下的一些細節。本篇將對Sun公司提供的HotSpot虛擬機(下面簡稱HS VM)的垃圾回收調優做一些簡單的介紹。 一、在開始了解HS VM之前,先了解一些垃圾回收的基礎理論。 1.垃圾檢測:
任何虛擬機的回收算法都包括兩個步驟:檢測垃圾和回收垃圾。當一個對象被創建時,其是活動的對象,此時其是可用的,而在運行過程中,該對象的引用不再被使
用,這時該對象就成為垃圾,一般采用兩種方式來區別活動對象和垃圾對象:引用計數和跟蹤。當一個對象被其它對象引用時,其引用計數加1,當不再被其它對象
引用時,計數減1,當其引用計數為0時,該對象就成為垃圾,引用計數的缺點是無法檢測循環引用和維護引用計數的代價,在現代虛擬機中一般不采用該方式。而
跟蹤指的是虛擬機追蹤從根對象出發的有向對象圖,如果一個對象不能被追蹤到,則該對象為垃圾,采用追蹤算法的垃圾回收器也叫標記并回收回收器。 2.避免堆碎片:
在進行垃圾回收之后,由于內存分配時,垃圾對象和活動對象可能相鄰存在,則可能會在堆中出現堆碎片,采用標記并回收回收器的虛擬機一般采用兩種方法來對付
堆碎片:壓縮和拷貝。壓縮回收是指當進行垃圾回收之后,將剩下的活動對象推向堆的另一端,則當垃圾回收完畢之后在另一端形成連續的堆。拷貝回收指的是保持
兩個同樣大小的堆,在對一個隊進行垃圾回收過程中,對于檢測出來的活動對象拷貝到另一個堆并連續存放。參見下圖
 3.分代回收
拷貝回收的一個缺點在于有些對象是長期存在的,但在回收過程中仍然必須將這些對象多次拷貝,分代回收指的是,對不同年齡(在內存存在的時間的長短)將對象
放入不同的代堆中,并對不同的代堆采用不同的回收算法。事實上,大部分的對象屬于“短命”對象,我們可以更多地對年輕的代堆進行垃圾回收,而對于年老的代
堆則減少回收頻度。 二、接下來我們介紹HS VM的一些垃圾回收調優。 1.順序垃圾回收器 JDK5.0
的VM支持四種垃圾回收器,默認的,HS VM采用順序垃圾回收器,順序垃圾回收器是一種single-threaded, stop-the-
world回收器,即單線程,非并行(運行時,阻塞其他操作)的回收器。當虛擬機初始化時,一個JVM所需的最大的內存區地址空間被保留,但并沒有馬上分
配內存而是需要時才分配(見下Virtual)。內存空間分為三個區域:年輕代區(Young)、年老代區(Tenured)和永久區(Perm)。其分
布見下圖 
年輕區分為三個區,一個Eden區和兩個Survivor區(拷貝回收),當對象創建時,放入Eden區,而總有一個Survivor區是空的,隨著每一
次的垃圾回收,從Eden區和非空Survivor區的存活對象放入空survior區,當一個對象經過多次回收仍然存在,則將其轉入年老區。而永久區用
于保存一些永久對象,譬如一個類的描述和方法,常量等等。 以下是一些設置參數例子(下面例子的值都是Solaris Operating Environment SPARC Platform Edition使用的默認值): 堆空間設置: -XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70 -Xms3670k -Xmx64m
第一個表示當空閑空間在代空間中比例低于40%時,將調整代空間將以使得空閑空間達到或者超過40%;第二個表示當空閑空間在代空間中比例高于70%時,
將調整代空間以使得空閑空間不超過70%;第三個表示虛擬機初始化時將至少分配3670K內存空間;第四個表示虛擬機最大使用的內存空間不超過64M。
HSVM的建議是:一般來說,對于大型應用,-Xmx64m是比較小的,應根據內存情況將其調整到盡量大的一個值。 代空間設置: -XX:NewRatio=2 -XX:NewSize=2228k -XX:MaxNewSize=unlimited(默認是不限制的,如果需設置,在該處填相應的值) -XX:SurvivorRatio=32
第一個表示Young空間和Tenured空間的比例是1比2;第二個和第三個分別表示Young空間的最小和最大的邊界;第四個表示Eden和
Surivor空間的比例,譬如32,則表示Survivor空間占Young空間的1/34,當Survivor空間太小時,當Survivor空間填
滿,則將剩余的部分填到Tenured空間,而當Survivor空間太大,則可能導致空閑空間太多,浪費內存,可能通過-XX:+
PrintTenuringDistribution輸出Young空間的對象在各個區的情況。HSVM的建議是:盡量給Young空間足夠多的空間,除
非你認為花在收集Tenured的時間過多,然而當Young空間超過堆空間的一半時,結果可能會適得其反,當增加處理器時,相應地增加Young空間的
大小,因為內存的分配可以并行進行。 2.其他垃圾回收器 HS VM其他的垃圾回收器包括:吞吐量回收器、并行回收器和漸進回收器(火車算法)。
吞吐量回收器對Young空間采用并行回收的策略,而Tenured空間則采用與順序回收器相同的策略,特點是吞吐量大(吞吐量是(工作時間—垃圾回收時
間)/工作時間),使用-XX:+UseParallelGC參數啟用吞 吐量回收器,當系統擁有多個處理器時可使用該參數,并可通過指定參數-XX:
ParallelGCThreads=<desired number>來設置回收線程數。 并行回收器對
Tenured空間采用并行回收的方式,特點是因回收垃圾而暫停的時間較小,使用-XX:+UseConcMarkSweepGC參數起用并行回收器,如
果與-XX:+UseParNewGC相結合,則Young空間也采用并行回收的方式,當你可以從垃圾回收時間較小中得到好處時,可使用該參數,譬如人機
交互用戶對反應時間比較敏感時可使用。 漸進回收器對Tenured空間采用漸進回收方式,即每一次回收并不對整個空間進行垃圾回收,而只
回收一部分,同樣的,其特點是回收垃圾而暫停的時間較小,然而,如果考慮到總的吞吐量,其回收速度比較Tenured空間默認回收器還慢,使用-
Xincgc參數啟用漸進回收器,當系統負載不重可以經常做垃圾回收時,使用該參數比較合適。 無論如何,系統調優都是一件艱難的工作,如上列出來的只是一個簡單的介紹和一些常用的配置,而具體的情況則需要在系統開發中根據相應的情況再做相應的調整,以獲得令人滿意的系統的性能
參考:《深入Java虛擬機》
http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html
2006-7-7
JVM啟動參數
虛擬機服務器模式/客戶機模式,使用server模式可以提高性能,啟動比client模式慢,長期運行則比client模式快。當該參數不指定時,虛擬
機啟動檢測主機是否為服務器,如果是則以server模式啟動,否則以client模式啟動,J2SE5.0檢測的根據是至少2個CPU和最低2GB內存 2.-agentlib:<lib-name>=<options> -agentpath:<lib-path>=<options>
本地類庫加載,當你的部分類包含一些本地方法時,需要自己編寫本地代碼并位于操作系統加載共享包(dll)的路徑上,如果你不喜歡將該包放在操作系統識別
的加載上,則可以通過指定這個參數來加載自己的本地共享包(dll)。不同之處在于-agentlib中僅指定包名,根據操作系統的不同虛擬機在一定路徑
上搜索該包,譬如對于windows平臺虛擬機在PATH路徑上搜索該包,而lib-path則是指定全路徑,例如 -agentlib:hprof 在windows平臺虛擬機會在啟動時到PATH路徑上搜索hprof.dll并加載 虛擬機在加載代理包之后有一個啟動的操作(詳細參見JDK參考),<options>指的是代理包的啟動參數 3.-classpath classpath -c classpath 指定類路徑,系統應用類加載器(ClassLoader)會到該路徑下加載類 4.-Dproperty=value 設置系統屬性,可以通過System.getProperty(property)獲得 5.-enableassertions[:<package name>"..." | :<class name> ] -ea[:<package name>"..." | :<class name> ] -disableassertions[:<package name>"..." | :<class ; ] -da[:<package name>"..." | :<class name> ]
啟用和停用斷言,默認是停用斷言。斷言指的是從JDK1.4開始在支持的關鍵字assert,assert(booleanvalue),當
booleanvalue為false時,拋出java.lang.AssertionError,必須指出的是,代碼編譯必須是1.4及其以上順從的,
即編譯時使用如下參數 java -source 1.4 一般僅在開發階段啟用斷言,而在運行階段不使用 其使用包括如下幾種情況 java -ea //啟動斷言 java -ea kname... //在包pkname及其子包下起用斷言 java -ea kname.classname //對類 pkname.classname啟用斷言 停用斷言與啟用設置類似 6.-enablesystemassertions -esa -disablesystemassertions -dsa 啟用和停用系統類斷言 7.-jar 運行包含在一個jar包里的程序,一般在jar包的/META-INF/MANIFEST.MF文件中指定Main-Class值為要運行的主函數,譬如 Main-Class:ayufox.ejb3.Test 8.-javaagent:<classname>[<=options>] 加載java語言代理,該功能是JDK5新增加的,可以通過該設置在JVM運行主函數(main)之前做一些預處理工作,其中classname中必須包含有靜態方法 public static void premain(String agentArgs, Instrumentation inst) { ... } 上面的options即是傳入該函數的代理參數agentArgs,關于Instrumentation詳細參見包java.lang.instrument 9.-verbose -verbose:class -verbose:gc -verbose:jni 在運行時 class:將類加載情況在控制臺中打印出來 gc:將虛擬機的垃圾回收事件信息打印 jni:放本地方法調用信息打印 -verbose與-verbose:class一樣 10.-version -showversion 顯示版本信息,不同在于第一種顯示版本后虛擬機結束退出 11.-? -help 顯示幫助信息并退出 12.-X 顯示非標準參數(見下面介紹)并退出 二、非標準參數(以-X開頭) 1.-Xint
所有字節碼以解析模式運行。第一代虛擬機即是以這種方式運行,由于需要Java解析器解析運行,所以效率比較低;第二代虛擬機則采用將字節碼編譯成本地代
碼的方式,效率大大提高;第三代虛擬機也叫自適應(HotSpot)虛擬機,通過監測代碼的執行情況檢測出代碼被頻繁執行的部分,將其盡量優化成本地代碼
方式運行,而對于普通部分,則采用解析的模式運行。 2.-Xbatch 禁止后臺編譯,一般HotSpot虛擬機在檢測到一段代碼為頻繁執行代碼需要將其編譯成本地代碼時,會啟動一個后臺線程完成這個工作,而同時采用解析的方式繼續運行字節碼。如果設置了該參數,則會停止繼續執行字節碼,先將其編譯成本地代碼,然后再繼續執行。 3.-Xdebug -Xnoagent -Xrun -Xrunjdwp 啟用調試模式,見前面的《利用JPDA構建調試平臺》這篇文章,后面將在一個獨立的文章中詳細介紹 4.-Xbootclasspath:bootclasspath -Xbootclasspath/a ath -Xbootclasspath/p ath
設置啟動根Classpath,即使啟動類加載器將在何處加載對象,關于類啟動加載器,參見《JVM類加載器體系結構》說明,分號后面的值指定路徑,以分
號隔開。其區別在于,-Xbootclasspath:bootclasspath將新的根加載路徑覆蓋默認的路徑(\jre\lib\rt.jar),
-Xbootclasspath/a ath將新的根加載路徑和原有的根加載路徑相結合,-Xbootclaspath/p ath將新的根加載路徑與原有的根加載路徑相結合,加載類時優先搜索該加載路徑 5.-Xcheck:jni 對本地調用(JNI)采用更嚴格的檢測方式,在進行JNI調用之前檢測數據和傳入參數,如果碰到不合法的數據則強制結束掉虛擬機,對運行性能有損害 6.-Xfuture 對類格式(class文件格式)采用更嚴格的檢測方式,以便向后兼容,最好在開發時采用該參數 7.-Xnoclassgc 不使用垃圾回收 8.-Xloggc:file 與-verbose:gc功能一樣,不同在于-Xloggc:file將信息記錄到一個文件,而-verbose:gc將其輸出到控制臺 9.-Xincgc -Xmsn -Xmxn -Xssn 跟內存分配和垃圾回收相關,-Xincgc表示采用漸進式垃圾回收,-Xmsn設置初始內存池大小,-Xmxn表示內存池允許的最大大小,-Xssn是線程棧大小,n是要設置的值,必須是1024的倍數,譬如 -Xms6291456 -Xmx83886080 -Xms6144k -Xmx81920k -Xms6m -Xmx80m 該部分對虛擬機的性能非常重要,在后面將有獨立的篇章詳細介紹 10.-Xprof -Xrunhprof[:help][:<suboption>=<value>,...] 在運行時剖析運行情況,并將剖析結果打印到控制臺,其中后一個可以指定特定剖析對象,譬如cpu,堆(heap)等,可以運行java -Xrunhprof:help獲得可以剖析的對象和取值 11.-Xrs 減少JVM對操作系統信號量的使用,J2SE1.3.1開始引入。 SUN
在J2SE1.3.0中增加了Java應用程序關閉時的回調鉤子(Hook),以便當JVM意外終止時用戶可以做一些資源清除工作。JVM監視控制臺事件
以實現JVM意外終止時的回調。JVM明確地注冊了一個控制臺控制處理器,當JVM接收到CTRL_C_EVENT,
CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, 或CTRL_SHUTDOWN事件時,該處理器介入關閉回掉鉤子
(HOOK)的處理。 如果虛擬機以服務的方式運行(譬如WEB服務器)當其收到CTRL_LOGOFF_EVENT事件,由于系統并
不會因此終止JVM進程,故JVM不可以進行終止的操作,然而這與如上產生了沖突(不結束卻又調用關閉回調鉤子),為了避免這個問題,從
J2SE1.3.1使用-Xrs以使JVM不再監測控制臺事件。
|
|
|
|