|
常用链接
留言簿(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 -eakname... //在包pkname及其子包下起用断言 java -eakname.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/aath -Xbootclasspath/path
设置启动根Classpath,即使启动类加载器将在何处加载对象,关于类启动加载器,参见《JVM类加载器体系结构》说明,分号后面的值指定路径,以分
号隔开。其区别在于,-Xbootclasspath:bootclasspath将新的根加载路径覆盖默认的路径(\jre\lib\rt.jar),
-Xbootclasspath/aath将新的根加载路径和原有的根加载路径相结合,-Xbootclaspath/path将新的根加载路径与原有的根加载路径相结合,加载类时优先搜索该加载路径 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不再监测控制台事件。
|
|
|
|