引言
現在很多的企業都在使用開源框架開發自己的企業級應用,如 Struts、Spring 和 Hibernate 等。起初由于受到資金和規模等的限制,大部分應用都部署在 Tomcat 或 Jboss 等開源應用服務器上。但隨著業務不斷發展,對應用部署的安全和性能要求也越來越高,企業希望將現有的開源應用從開源服務器遷移到商業應用服務器之上,比如:WebSphere Application Server ( 以下簡稱為 WAS),通過 WAS 增強應用整體性能,并實現更加可靠的管理。本文將通過實例向大家介紹如何將開源應用從 Tomcat 遷移到 WAS,并幫助大家解決一些可能遇到的普遍問題。
基于 Eclipse 開發的 Struts、Spring 和 Hibernate 開源應用和開發環境的特點
隨著 Java 技術的逐漸成熟與完善,作為建立企業級應用的標準平臺,J2EE 平臺得到了長足的發展。借助于 J2EE 規范中包含的多項技術:Enterprise JavaBean (EJB)、Java Servlets (Servlet)、Java Server Pages (JSP)、Java Message Service (JMS) 等,大量的應用系統被開發出來。但是,在傳統 J2EE 應用的開發過程中也出現了一些問題,比如在存儲和讀取過程中使用大量 SQL 和 JDBC 操作,會降低編程的效率以及系統的可維護性;過去傳統的 J2EE 應用多采用基于 EJB 的重量級框架 ( 比如:EJB 2.1),這樣做的問題在于使用 EJB 容器進行開發和調試需要耗費大量時間并且耦合度非常高,不利于擴展。
在摸索過程中,各種開源框架孕育而生。開源框架以其免費、開源和簡單等特點逐漸成為開發人員的最愛,現在仍然有很多的企業使用開源框架開發自己的應用程序。在開源框架中使用最多的就是 Struts、Spring 和 Hibernate 整合框架 ( 以下簡稱 SSH 框架)。
典型的 J2EE 三層結構,分為表現層、中間層(業務邏輯層)和數據服務層。三層體系將業務規則、數據訪問及合法性校驗等工作放在中間層處理。客戶端不直接與數據庫交互,而是通過組件與中間層建立連接,再由中間層與數據庫交互。下面就介紹以下 SSH 框架在 J2EE 三層結構中的作用:
- Struts 是一個在 JSP Model2 基礎上實現的 MVC 框架,主要分為模型 (Model) 、視圖 (Viewer) 和控制器 (Controller) 三部分,其主要的設計理念是通過控制器將表現邏輯和業務邏輯解耦,以提高系統的可維護性、可擴展性和可重用性。
- Spring 是一個解決了許多 J2EE 開發中常見問題并能夠替代 EJB 技術的強大的輕量級框架。這里所說的輕量級指的是 Spring 框架本身,而不是指 Spring 只能用于輕量級的應用開發。Spring 的輕盈體現在其框架本身的基礎結構以及對其他應用工具的支持和裝配能力。與傳統 EJB ( 比如 EJB 2.1) 相比,Spring 可使程序研發人員把各個技術層次之間的風險降低。當然,隨著 Java EE 5 及 Java EE 6 中新 EJB 規范的出現,如:EJB 3.0, EJB 3.1,EJB 的開發變得越來越簡單。用戶可以根據自己的需求和能力,選擇合適的框架。想了解更多關于 Java EE 5 和 Java EE 6 中的內容,請參考參考資源 [4] 和 [5]。
- Hibernate 是一個數據持久層框架,是一種實現對象和關系之間映射 (O/R Mapping) 的工具,它對 JDBC 進行了輕量級的對象封裝,使程序員可以使用對象編程思想來操作數據庫。它不僅提供了從 Java 類到數據表的映射,也提供了數據查詢和恢復機制。相對于使用 JDBC 和 SQL 來操作數據庫,使用 Hibernate 能大大的提高開發效率。
SSH 框架雖然非常強大,但也有一些缺點,比如 : 相比 Servlet+JDBC 開發方式,復雜度增加了不少 ; 而且開源框架開發和部署的靈活性,使得其使用方式不是很符合現有的 J2EE 規范,從而導致從 Tomcat 或其他開源服務器上遷移到 WAS 會出現很多問題和異常。并且,因為默認的 Eclipse 或 MyEclipse 工具缺少 WAS 的運行時插件,使得開發的開源應用程序無法直接從 IDE 里部署到 WAS。接下來,我們會分步介紹從 Tomcat 遷移到 WAS 可能出現的問題,雖然不能涵蓋遷移過程中的所有問題,但希望能夠拋磚引玉,盡量解決一些普遍存在的問題。
以下使用的實例是利用 Struts2+Spring2+Hibernate3 開發的模擬醫院管理應用。其中功能模塊包括前臺的顯示模塊、登錄模塊、后臺的文章和藥品管理模塊、用戶管理模塊等基本模塊;數據庫包括藥品、文章、學生、教師和看病等數據表。我們利用 Struts 實現 MVC 模型處理前臺的各種請求;利用 Hibernate 將數據持久化并簡化對數據的查詢;利用 Spring 進行依賴注入控制整個業務邏輯層。圖 1 為應用的部分包和配置文件結構
圖 1. 應用部分包結構和相關配置文件
配置好 MYSQL,將應用部署到 Tomcat 正常顯示頁面如下:
圖 2. 應用主頁
這里需要注意的是,由于開源框架的開發和目錄結構不規范,導致在 WAS 中部署 WAR 文件失敗。您可能會看到諸如“EAR 文件可能已損壞或不完整。確保對于 WebSphere Application Server,該應用程序處于兼容的 Java 2 Platform, Enterprise Edition (J2EE) 級別。”這樣的錯誤。
圖 3. WAS 中應用部署錯誤
遇到上述錯誤的原因,可能是因為 WAR 文件中包含 EXE 文件或者 WAR 文件結構不規范,去掉這些文件或調整文件結構即可解決該錯誤。
遷移之前的準備工作
遷移之前的準備工作非常關鍵。我們首先要確保應用可以在 Tomcat 成功運行,當然我們還需要確認以下幾個方面:
- Tomcat 啟動正常
- Struts、Spring 和 Hibernate 所需要的 lib 包都包含在應用的 WAR 或者 EAR 包中
- 應用在 Tomcat 上部署并且啟動成功
- 應用的 Struts 功能啟動成功,并能正常處理請求
- 應用的數據庫連接正常,Hibernate 映射成功并能正常實現數據持久化
- 應用的 Spring 功能成功,并能將所需要的資源注入到應用中
- 測試應用的其他功能確保應用整體運行正常
同時查看 Tomcat、Eclipse 的日志,確保應用沒有編譯異常或錯誤。當然我們利用 Eclipse 或者 MyEclipse 開發應用時可能會用到 WAS 的插件,我們在部署之前一定要確保系統中只有一個 WAS 實例在運行。如果其他 WAS 實例運行,可能會出現端口沖突等錯誤,這時 WAS 會提示一些錯誤:
清單 1. WAS 端口沖突錯誤
org.omg.CORBA.INTERNAL: CREATE_LISTENER_FAILED_4 vmcid: 0x49421000 minor code:
Caused by: org.omg.CORBA.INTERNAL: CREATE_LISTENER_FAILED_4 vmcid: 0x49421000 minor
code: 56 completed: No
at com.ibm.ws.orbimpl.transport.WSTransport.createListener(WSTransport.java:719)
at com.ibm.ws.orbimpl.transport.WSTransport.initTransports(WSTransport.java:591)
at com.ibm.rmi.iiop.TransportManager.initTransports(TransportManager.java:155)
at com.ibm.rmi.corba.ORB.set_parameters(ORB.java:1212)
at com.ibm.CORBA.iiop.ORB.set_parameters(ORB.java:1662)
at org.omg.CORBA.ORB.init(ORB.java:364)
at com.ibm.ws.orb.GlobalORBFactory.init(GlobalORBFactory.java:86)
at com.ibm.ejs.oa.EJSORBImpl.initializeORB(EJSORBImpl.java:179)
at com.ibm.ejs.oa.EJSServerORBImpl.<init>(EJSServerORBImpl.java:102)
at com.ibm.ejs.oa.EJSORB.init(EJSORB.java:55)
at com.ibm.ws.runtime.component.ORBImpl.start(ORBImpl.java:379)
... 26 more
|
WAS 在啟動的時候拋出以上異常,這主要是因為端口沖突,我們可以查看系統中是否有已經啟動的 WAS 或者別的程序正在占用此端口。也可以通過修改概要文件 config\cells\cellname\nodes\nodename 目錄下的 serverindex.xml 文件中的端口解決。
應用和環境都沒問題了,我們就可以著手遷移應用了。
部署中 Struts 框架可能遇到的問題所遇到的問題
Struts 框架最早是作為 Apache Jakarta 項目的組成部分問世運作,它繼承了 MVC 的各項特性,并根據 J2EE 的特點,做了相應的變化與擴展。Struts 框架很好的結合了 Jsp,Java Servlet,Java Bean,Taglib 等技術。
不論是 Struts1 還是 Struts2,很多部署問題都跟 lib 包沖突有關,所以在部署應用的時候要盡量查看一下應用 WAR 文件本身是否存在相互沖突的 jar 文件,WAR 文件和 WAS 所帶的包是否存在相互沖突。我們總結了一些 Struts 包沖突的問題供大家參考:
啟動應用的時候報 Unable to load bean typecom.opensymphony.xwork2.ObjectFactory classorg.apache.struts2.impl.StrutsObjectFactory 錯誤。這種錯誤多是由于 WAS 中存在相同 sturts2-core jar 文件與應用 WAR 或者 EAR 文件中 struts 包沖突。建議刪除 WAR 包中的 jar 文件,即可解決此問題。
還有一種情況是在 Tomcat 下項目運行沒有任何問題,但把 WAR 包安裝在 WAS 中只能訪問 HTML 頁面了,其余的 Struts2 的請求和 JSP 頁面都不能訪問,提示您無權查看此頁面,查看 WAS 日志文件中發現,啟動時有類似錯誤:
清單 2. WAS 包沖突錯誤
[11-8-18 15:17:41:079 CST] 00000010 webapp E com.ibm.ws.webcontainer.webapp.WebApp
initializeExtensionProcessors SRVE0280E:
擴展處理器無法在工廠
[com.ibm.ws.jsp.webcontainerext.ws.WASJSPExtensionFactory@2bec2bec]
中進行初始化:java.lang.ClassCastException:
com.sun.faces.application.WebappLifecycleListener
incompatible with java.util.EventListener
……
[10-8-18 15:17:41:562 CST] 00000010 config I Initializing
Sun's JavaServer Faces implementation (1.2_07-b03-FCS) for context '/cc'
[10-8-18 15:17:44:579 CST] 00000010 webapp W com.ibm.ws.webcontainer.webapp
.WebApp initializeTargetMappings SRVE0269W: 找不到用于處理 JSP 的擴展處理器。
|
解決方法有兩種:
- 在應用程序服務器 -> [ 選擇所使用的服務器 ] -> Web 容器設置 -> Web 容器 -> 定制屬性,增加名稱為"com.ibm.ws.webcontainer.invokefilterscompatibility"的定制屬性,值設為 true。
- 或者檢查 WAR 文件的 lib 庫中是否存 jsf-api.jar,jsf-impl.jar,jstl-1.2.jar 三個 jar 文件。這是因為在使用 MyEclipse 開發時,MyEclipse 會自動將這三個 jar 文件加入到 lib 庫中,但 jsf-impl.jar 包中的 com.sun.faces.application.WebappLifecycleListener 與 java.util.EventListener 不兼容導致應用無法訪問,打開 WAR 包的 lib 目錄,刪除這三個 lib 包即可解決問題。
部署中 Spring 框架可能遇到的問題所遇到的問題
Spring 框架支持輕量級的企業應用開發。作為開源項目,Spring 框架得到了廣泛的支持。目前大多數 SSH 架構的開源應用以 Tomcat 作為開發以及測試的 Web 容器。 WAS 也同樣支持基于 Spring 框架的項目開發和部署,除了支持 Spring 框架本身的資源管理以及支持事務的特性外,WAS 也可以依靠自身的容器管理、事務支持等帶來更加可靠的運行時環境。本文該部分將初步介紹基于 Spring 框架的開源應用從 Tomcat 上移植到 WAS 上需要注意的方面。
使用 Spring 開發的應用可以同時使用 Struts 和 Hibernate。整合了 Struts、Spring 和 Hibernate 進行開發的應用只要保證需要的 JAR 包全部打包在應用中,便可以正確的部署到 WAS 上。其中,WAS 不提供 Tomcat 缺省提供的一些開源 JAR 包,需要將這些包包含在應用中。此外,WAS 提供了很多 J2EE 相關特性,如果 Spring 要使用這些特性,則需要對 Spring 做相關配置。
數據訪問
Spring 整合 Hibernate 時,數據源配置信息應該定義在 applicationContext.xml 文件中。清單 3 給出了一個典型的配置數據源信息的示例。您可以將這段代碼不做任何修改,加入到 applicationContext.xml 文件中,并放到打包之后的應用中,WAS 會自動識別并利用 Spring 框架完成數據源的配置。
清單 3. applicationContext.xml 文件中的數據源配置
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver"></property>
<property name="url" value="jdbc:db2://localhost:50000/MYTEST"></property>
<property name="username" value="db2admin"></property>
<property name="password" value="password"></property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.DB2Dialect</prop>
</props>
</property>
<property name="mappingResources">
<list><value>com/ibm/user/Person.hbm.xml</value></list>
</property>
</bean>
|
您也可以使用 WAS 中已經配置好的數據源,在 Spring 的配置文件 applicationContext.xml 文件中聲明數據源的代理 Bean,將 WAS 的數據源等資源通過該 Bean 委托給 Spring 框架進行調用。清單 4 給出了一個使用該方式進行配置的 applicationContext.xml 文件的片段。應用在運行時會使用該 Bean 找到相應的數據源完成與數據庫的交互。
清單 4. applicationContext.xml 文件中配置 WAS 數據源
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value> java:comp/env/jdbc/SSHTestDB</value></property>
</bean>
|
事務管理
Spring 框架同 WAS 一樣支持兩種事務管理的方式,分別為編程式和聲明式。大多數的用戶會選擇聲明式的事務管理方式,這種方式也是推薦使用的。
通常情況下 Spring 事務管理的一個核心是 PlatformTransactionManager 接口,使用聲明方式的事務管理的類均實現該接口,如對數據源進行事務管理的 DataSourceTransactionManager,對 Hibernate 進行事務管理的 HibernateTransactionManager 等。用戶可以選擇繼續使用這些事務管理方法,在 applicationContext.xml 文件中做相應的配置,如清單 5 所示,然后打包部署到 WAS 上完成應用的安裝和配置。
清單 5. Spring 框架的事務管理配置
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
|
其中 ref=”dataSource” 對應于清單 3 中的 Bean id=”dataSource”;ref=”sessionFactory”對應于清單 3 中的 bean id="sessionFactory"。
WAS 支持 JTA,而 Spring 也提供了 JtaTransactionManager。因此也可以將 Spring 中的事務管理交給 WAS 來做。Spring 2.5 之后提供的特定于 WAS 的事務管理的實現類為 WebSphereUowTransactionManager,您可以在 applicationContext.xml 文件中進行相應的配置,將事務的管理交由 WAS 來做。如清單 6 所示。其中配置的 bean id="transactionManager" 并不需要知道自己為哪些資源負責,因為它使用了 WAS 容器的全局事務管理體系。
清單 6. Spring 配置 WAS 的事務管理
<bean id="txManager"
class="org.springframework.transaction.jta.WebSphereUowTransactionManager">
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref local="txManager"/>
</property>
</bean>
|
部署中 Hibernate 框架可能遇到的問題所遇到的問題
Hibernate 做為數據持久層框架非常靈活,易于上手,并且便于與其它開源框架整合,從而使它成為開源解決方案中數據持久層框架的不二選擇。Hibernate 從 3.2 開始,就開始兼容 JPA。Hibernate3.2 獲得了 Sun TCK 的 JPA (Java Persistence API) 兼容認證。這使它的應用范圍更加廣泛。這里介紹將使用 Hibernate 做為持久層的應用移植到 WAS 上時需要注意的方面。
在項目初期,由于定位或需求的原因,很多 Hibernate 應用都使用 Tomcat 或 Jboss 做為應用服務器。隨著項目規模越來越大,對應用的可靠性和安全要求越來越高,就會考慮向商業應用服務器的遷移。總的來說,對一個可以正常運行在 Tomcat 或 Jboss 上的應用來說,移植到 WAS 上非常簡單,并不需要做太多改動,只需要將應用所依賴的 Hibernate 相關 jar 包都打包在應用中,再更具情況對配置文件做輕微調整即可,不用修改任何 Java 源代碼。
保持原有連接方式
很多 Hibernate 應用采用 JDBC 的鏈接方式,即在配置文件 hibernate.cfg.xml 中配置 connection.url 屬性,指定數據庫鏈接信息。例如:<property name="connection.url">jdbc:db2://db2url:port/dbname</property>
這種應用程序往往還要使用第三方提供的數據庫連接池,例如 C3P0 等。如果在移植到 WAS 之后仍然想保持現有連接形式和數據庫連接池不變,則不需要對配置文件做任何修改,只需要將第三方提供的數據庫連接池所依賴的 jar 包文件一同打包到 WAS 應用中即可。例如將 C3P0 數據庫連接池 jar 文件 c3p0-0.9.1.jar 打包到應用程序 lib 目錄下。
使用 WebSphere Application Server 數據源
WAS 數據源有著眾多企業級優勢,很多用戶移植后都希望能使用到 WAS 做為企業級應用服務器數據層的強大功能,對 Hibernate 的移植也不例外。其實將 Hibernate 應用的數據源移植到 WAS 非常簡單,只需要在 hibernate.cfg.xml 配置文件中加入數據源屬性 connection.datasource 和 JNDI 提供商信息即可,無需修改任何源代碼。這里需要注意,一旦配置好 WAS 的數據源,WAS 將接管與數據庫通信的工作,如果在您以前的應用中使用了第三方數據庫連接池,將會產生沖突。解決方法也很簡單,只要將 hibernate.cfg.xml 配置文件中的關于第三方數據庫連接池的信息注釋或刪除即可。
總結起來可分為三步:
- 將數據庫連接信息
<property name="connection.url">jdbc:db2://db2url:port/dbname</property>
替換為 WAS 數據源信息
<property name="connection.datasource">jdbc/hibernate</property>
- 加入 jndi.class 屬性
<property name="jndi.class">com.ibm.websphere.naming.WsnInitialContextFactory</property>
- 注釋或刪除原有數據庫連接池相關屬性
總結
現在的開源框架越來越龐大,同一框架不同版本的區別也很大。這篇文章雖不能覆蓋開源框架遷移到 WAS 的所有問題,但總結的都是一些比較普遍的問題,力求讓用戶快速地發現和解決部署和遷移過程中的問題。在遷移過程中,應用的代碼基本不需要修改,只要配置和部署得當,從 Tomcat 將開源應用遷移到 WAS 并不是一件難事。
摘要: Spring配置文件中關于事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。 DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際為SessionFactory,Transact...
閱讀全文
從J2SE 5.0開始,HotSpot JVM共包含四種垃圾收集器,它們全部基于分代算法。
一、代的劃分
HotSpot JVM中內存被劃分為三代:年幼代(young generation)、年長代(old generation)和永久代(permanent generation)。從邏輯上講,年幼代和年長代共同構成了Java堆,而永久代則被稱為方法區(method area)。除了一些大對象可能在年長區中直接分配外,大部分對象都在年幼區中創建;而年長區除了那些直接創建的大對象外,大部分對象都是在年幼區中歷經幾次垃圾收集而幸免于難后被提升過來的。永久代中則保存著已載入類型的相關信息,包含了類、方法和其他一些內部使用的元數據,所有這些信息同樣以對象的形式來組織和表示,雖然這些對象并不是Java對象,但是它們卻象Java對象一樣可以被同樣的垃圾收集器所收集;另外,java.lang.String類所管理的內在化的字符串緩存池也在該代中分配;雖然名字叫做“永久”代,但其中的對象并不是永久的,只是沿用了歷史名稱而已。
年幼代由一個伊甸區(Eden Space)和兩個更小的生還區(Survivor Space)組成,如下圖所示(該圖為縮略圖,請點擊察看原圖)。大部分對象在伊甸區中創建,少數大對象可能在年長代中直接創建。生還區中保存的對象是歷經一次或多次對年幼代的垃圾收集而未死的幸存者,并且在被認為已足夠成熟而提升到年長代之前,它們仍有在稍后的某次垃圾收集過程中犧牲的可能。除非處在垃圾收集過程當中,兩個生還區中只有一個用來容納這些幸存者,另一個則保持為空。
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s5.sinaimg.cn/bmiddle/51501580g8169fb66ab44&690"> 二、垃圾收集類型
年幼代填滿后,一次只針對該代的收集被執行,這樣的收集也被稱作“次收集(minor collection)”。當年長代或永久代被填滿后,一次針對所有代的完整收集被執行,這樣的收集也被稱作“主收集(major collection)”。通常來說,在一次主收集過程中,年幼代首先被收集,收集算法采用當前收集器的年幼代收集算法,該算法往往是針對年幼對象的行為特征而專門設計的;然后是對年長代和永久代的收集,收集算法都采用當前收集器的年長代收集算法。對于給定收集器所具體使用的年幼代和年長代收集算法,請參考下文。另外,主收集過程中如果存在壓縮,則每代獨自進行。
不過,首先收集年幼代的策略在年長代空閑空間太小時會失效,因為年長代已無足夠的空間來接納所有的可能從年幼代提升過來的對象;在這種情況下,除CMS外的所有收集器都會放棄原有的年幼代收集算法,轉而統一采用年長代收集算法對所有代進行收集。(CMS收集器之所以例外是因為它的年長代算法不能用來收集年幼代。)
三、快速分配
從下文對垃圾收集器的描述中可以看出,在許多情況下,內存中都有大塊的連續空閑空間用以滿足對象的分配請求。這種情形下的分配操作使用簡單的“bump-the-pointer”技術,效率很高。按照這種技術,JVM內部維護一個指針(allocatedTail),它始終指向先前已分配對象的尾部,當新的對象分配請求到來時,只需檢查代中剩余空間(從allocatedTail到代尾geneTail)是否足以容納該對象,并在“是”的情況下更新allocatedTail指針并初始化對象。下面的偽代碼具體展示了從連續內存塊中分配對象時分配操作的簡潔性和高效性:
void * malloc(int n){
if( geneTail - allocatedTail < n ) doGarbageCollection();
void * wasAllocatedTail = allocatedTail;
allocatedTail += n;
return wasAllocatedTail;
}
對于多線程應用,分配操作必須是線程安全的。如果使用全局鎖為此提供保證,則分配操作必定成為一個性能瓶頸。基于此,HotSport JVM采用了一種被稱為“線程局部分配緩沖區”(Thread-Local Allocation Buffers,TLAB)的技術。該項技術為每個線程提供一個獨立的分配緩沖區(伊甸區的一小部分),借此來提高分配操作的吞吐量。因為針對每個TLAB,只有一個線程從中分配對象,故而分配操作可以使用“bump-the-pointer”技術快速完成,而不必使用任何鎖機制;只有當線程將其已有TLAB填滿并且需要獲取一個新的TLAB時,同步才是必須的。同時,為了減少TLAB所帶來的空間消耗,還使用了一些其他技術,例如,分配器能夠把TLAB的平均大小限制在伊甸區的1%以下。
“bump-the-pointer”和TLAB技術的組合保證了分配操作的高效性,類似new Object()這樣的操作在大部分時間內只需要大約10條機器指令即可完成。
四、收集方式
1)串行(serial)和并行(parallel)
串行和并行是從收集任務本身如何被完成的角度來描述收集過程的。采用串行方式收集時,同一時間只有一件事情會發生。例如,即使有多個CPU可用,還是只有一個被用來執行收集任務。當采用并行方式收集時,垃圾收集任務被分成許多子任務,并且那些子任務在不同的CPU上被同時執行。同時操作使收集任務得以更快地完成,代價是增加了任務的復雜性并可能產生內存碎片。2)STW(stop-the-world)和并發(concurrent)
STW和并發是從收集任務是否影響應用程序執行的角度來描述收集過程的。當垃圾收集使用STW方式進行時,在收集期間應用程序將被完全掛起。作為另一種選擇,收集任務可以采取并發方式,與應用程序一起同時執行。比較典型的情況是,并發收集器采取并發方式完成大部分工作,但也可能偶爾地不得不切換到STW方式,以完成一些需要應用程序短暫停頓的工作。STW收集比并發收集更為簡單,因為在收集期間堆被凍結、對象不會改變;它的缺點是對某些應用程序來說,被暫停過久是不符合要求的。相對地,采用并發方式進行垃圾收集時,停頓時間會更短,但這也要求收集器必須更加小心,因為它正在操作可能被應用程序同時更新的對象。對并發收集器來說,這會增加額外開銷從而影響性能,也會對堆空間產生更大的需求。
五、串行收集器(serial collector)
使用串行收集器時,對年幼代和年長代的收集都采用串行、STW方式進行。也就是說收集任務同時只使用一個CPU,而且在收集任務執行期間,應用程序的執行被中止。
1)串行收集器如何收集年幼代?
下圖展示了使用串行收集器對年幼代進行收集時的操作細節。伊甸區中的活動對象被拷貝到初始為空的生還區2中;已占用的生還區1中的活動對象如果仍顯年輕,則同樣被拷貝到生還區2中,否則被直接拷貝到年長代;需要注意的是,生還區2一旦被填滿,則所有尚未被拷貝的活動對象,不論其來自伊甸區還是生還區1,也不論其曾經幸免于多少次次收集,統統被拷貝到年長代。按照定義,在活動對象被拷貝之后,伊甸區和生還區1中的對象就全部成為垃圾對象,無須再被檢查。(垃圾對象在圖中以“X”標記,雖然實際上收集器并不檢查和標記這些對象。)
![HotSpot <wbr alt=]()
JVM中的垃圾收集" alt="HotSpot
JVM中的垃圾收集" src="http://s4.sinaimg.cn/middle/51501580g81cfe5708d93&690"> 收集完成后,伊甸區和生還區1變為空閑空間,活動對象保存在生還區2中。此時,兩個生還區的角色已經發生了互換。如下圖:
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s4.sinaimg.cn/middle/51501580g81d362167ce3&690"> 2)串行收集器如何收集年長代?
串行收集器采用標記-清理-壓縮算法收集年長代和永久代。在標記階段,收集器遍歷引用樹,找出所有活動對象并打上標記;在清理階段,順序掃描代空間中所有對象(不論死活),計算出每個活動對象在代空間中的新位置;在壓縮階段,指向活動對象的所有引用被先期更新后,所有活動對象也被逐個滑動到其新的位置。由于所有活動對象都是按照次序朝代空間頭部移動的,因此就在代空間尾部自然形成了一個單一而連續的空閑空間。如下圖:
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s3.sinaimg.cn/middle/51501580g81fd1aa883d2&690">壓縮過程使基于年長代或永久代的分配操作可以使用快速的“bump-the-pointer”技術。
3)何時使用串行收集器?
對于運行在客戶端級硬件上并且對停頓時間沒有特別要求的大多數應用而言,串行收集器都是首選。按照目前的硬件水平,串行收集器可以高效地管理使用64MB堆空間、最長停頓時間不能超過半秒的很多重要應用。
4)串行收集器的選用
在J2SE 5.0版本中,對于非服務器級硬件而言,串行收集器作為缺省的垃圾收集器被自動選用;對于其他硬件平臺,則可以通過命令行選項“-XX:+UseSerialGC”進行顯示的選用。
六、并行收集器(parallel collector)
目前,許多Java應用的運行平臺大都包含很多物理內存和多個CPU。并行收集器,也被稱作吞吐量收集器,被開發出來的主要目的就是為了充分利用CPU資源,而不是只讓一個CPU去做垃圾收集而其他CPU卻被閑置。
1)并行收集器如何收集年幼代?
和串行收集器相比,并行收集器采用了大致相同的年幼代收集算法,只是執行的是其并行版本而已。對年幼代的收集雖然仍基于拷貝技術、采用STW方式進行,但收集工作是并行展開的,使用了多個CPU,這就降低了垃圾收集開銷,從而提高了應用程序的吞吐量。下圖展示了并行收集器和串行收集器在執行年幼代收集時到底有何不同:
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s7.sinaimg.cn/middle/51501580g81e435e495a6&690">
2)并行收集器如何收集年長代?
和串行收集器一樣,并行收集器對年長代的收集同樣基于標記-清理-壓縮算法,同樣采用串行、STW方式進行。
3)何時使用并行收集器?
能夠得益于并行收集器的應用程序,必定運行在多CPU機器上,并且對停頓時間不能有特別的約束。因為可能持續時間很長的年長代收集雖然稀少,但還是會發生的。適于采用并行收集器的典型應用包括批處理、記帳、工資單和科學計算等等。你可能更傾向于選擇并行壓縮收集器(見下文)而不是并行收集器,因為前者對所有代(而不只是年幼代)的收集都采用并行方式進行。
4)并行收集器的選用
在J2SE 5.0版本中,對于服務器級硬件而言,并行收集器作為缺省的垃圾收集器被自動選用;對于其他硬件平臺,則可以通過命令行選項“-XX:+UseParallelGC”進行顯示的選用。
七、并行壓縮收集器(parallel compacting collector)
并行壓縮收集器在J2SE 5.0 U6中引入,它和并行收集器的區別在于,對年長代的收集它使用了全新的算法。注意:并行壓縮收集器終將取代并行收集器。
1)并行壓縮收集器如何收集年幼代?
同并行收集器,不再贅述。
2)并行壓縮收集器如何收集年長代?
使用并行壓縮收集器時,對年長代和永久代的收集都采用帶滑動壓縮的準并行、STW方式進行。為了滿足并行處理的要求,每一個代空間均被邏輯劃分為諸多定長區域(fixed-sized region),每個區域的相關信息保存在收集器維護的內部數據結構中。收集過程被分為標記、匯總和壓縮三個階段進行。在標記階段,根引用集被劃分給多個垃圾收集線程,它們同時運行,以并行的方式對活動對象進行追蹤和標記;在活動對象被標記的同時,該對象的起始區域的數據也將被同步更新以反映該活動對象的大小和位置信息。
在匯總階段(summary phase),操作不再基于對象,而是區域。考慮到先前收集過程中的壓縮累積效應,每一個代空間中位于左側的某一部分通常是密集的,主要包含了活動對象。從這樣的密集區塊中可能回收的空間數量使得它們并不值得被壓縮。所以匯總階段的首要任務就是檢查區域的密集度,從最左邊一個區域開始,直到找到這樣的一個區域,使得在該區域及其右側所有區域中可被回收的空間數量抵得上對它們進行壓縮的成本。該區域左側的所有區域就被稱為密集前置區塊,沒有對象會被移入其中。該區域及其右側所有區域會被壓縮,以消除所有死區。匯總階段的下一個任務就是計算并保存每個被壓縮區域中活動數據的首字節在壓縮后的新位置。需要注意的是:匯總階段在目前被實現為一個串行階段,這也是“準”并行方式的由來;并行實現也是可能的,只是與標記和壓縮階段的并行化相比,它對性能的影響不大。
在壓縮階段,垃圾收集線程使用匯總數據確定需要被填充的區域,然后它們就可以獨立地把對象拷貝到這些區域中而不再需要額外的同步。這就產生了一個堆,堆空間的一端塞滿了活動對象,而另一端則是一個單一而連續的空閑內存塊。
3)何時使用并行壓縮收集器?
和并行收集器一樣,并行壓縮收集器同樣有益于在多CPU機器上運行的應用程序。除此之外,年長代收集的并行化操作方式還減少了停頓時間,使得并行壓縮收集器比并行收集器更為適合那些有停頓時間限制的應用。不過,對于運行在大型共享主機(如SunRays)上的應用來說,并行壓縮收集器也許并不太合適,因為任何單一應用都不應長時間獨占幾個CPU。在這樣的機器上,要么考慮通過命令行選項“-XX:ParallelGCThreads=n”減少垃圾收集線程的數目,要么考慮選擇一種不同的收集器。
4)并行壓縮收集器的選用
并行壓縮收集器只能通過命令行選項“-XX:+UseParallelOldGC”進行顯示的選用。
八、并發的標記-清理收集器(Concurrent Mark-Sweep(CMS) Collector)
對于許多應用來說,端到端的吞吐量并不象響應時間那么重要。通常來講,對年幼代的收集并不會引起太長時間的停頓。但是對年長代的收集,雖然不常發生,卻可能導致停頓時間過長的狀況,在堆空間很大時尤其明顯。為了解決這個問題,HotSpot JVM包含了一個名叫“并發的標記-清理(CMS)收集器”的收集器,它也被稱為低延遲收集器。
1)CMS收集器如何收集年幼代?
同并行收集器,不再贅述。
2)CMS收集器如何收集年長代?
采用CMS收集器收集年長代時,大部分收集任務與應用程序并發執行。
CMS收集器的收集周期始于初始標記,它采用串行、STW方式進行,用于確定根引用集。隨后進入并發標記階段,完成對所有活動對象的追蹤和標記,在JDK6中該階段已開始采用并行、并發方式進行。由于在并發標記過程中應用程序正在執行并可能更新了一些對象的引用域,因此并發標記過程結束時并非所有活動對象都已確保被標記出來。為了處理這種情況,應用程序再次暫停,收集過程進入再標記階段;它采用并行、STW方式進行,通過對并發標記過程中被修改對象的再次訪問最終完成整個標記過程。因為再標記階段的停頓時間往往是最長的(超過初始標記停頓甚至次收集停頓),因此再標記過程會盡量采用并行方式進行。
再標記階段完成后,所有活動對象都已確保被標記,隨后進入并發清理階段,它采用串行、并發方式進行,就地回收所有垃圾對象。下圖展示了串行的標記-清理-壓縮收集器和CMS收集器在收集年長代時的區別:
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s13.sinaimg.cn/middle/51501580g82676852d74c&690"> 因為一些任務(例如再標記過程中對被修改對象的再次訪問)增加了收集器的工作量,CMS收集器的總體開銷自然會增大。對于大多數試圖減少停頓時間的收集器來說,這是一種典型的折衷。
CMS收集器是唯一一個不使用壓縮技術的收集器。也就是說,在垃圾對象所占用的空間被釋放以后,收集器并不把活動對象全部推擠到代空間的某一端去。見下圖:
JVM中的垃圾收集" alt="HotSpot JVM中的垃圾收集" src="http://s3.sinaimg.cn/middle/51501580g82716466bed2&690">這種方式節省了回收時間,但卻因為空閑空間不再連續,收集器也就不再可能只使用一個簡單指針即可指示出可分配給新對象的下一個空閑空間的位置,相反,它現在需要使用空閑空間列表。也就是說,收集器創建并通過一組列表把內存中尚未分配的區域連接起來,每當有對象需要分配空間時,適當的列表(基于所需內存數量)被搜索,以找到一塊足以放下該對象的空閑區域。作為結果,與使用“bump-the-pointer”技術時相比,年長代中的分配操作變得更加昂貴。同時這也給年幼代收集帶來了額外的開銷,因為在其收集過程中每提升一個對象都會觸發一次年長代中的分配操作。
CMS收集器的另一個缺點是和其他收集器相比它需要更大的堆空間。一方面,由于在標記階段應用程序被允許運行,它就可能繼續分配內存,從而可能使年長代空間不斷地增長;另一方面,雖然標記階段完成后所有活動對象都已確保被標記,但是在標記過程中一些對象卻可能變為垃圾對象,而且直到下次年長代收集之前它們不會被回收。這樣的對象也被稱為游浮垃圾。
CMS收集器的最后一個缺點是由于缺乏壓縮它可能引發碎片化問題。為了對付碎片化,CMS收集器跟蹤對象的流行尺寸,預估未來需求,并為滿足需求還可能分割或合并空閑內存塊。
不象其他收集器,CMS收集器并不是等到年長代填滿后才啟動對年長代的收集,而是嘗試盡早啟動年長代收集,以便在年長代被填滿之前收集過程可以完成。否則的話,CMS收集器將重新采用在串行和并行收集器中使用的標記-清理-壓縮算法,盡管該算法工作于STW方式,也更加耗時。為避免這種情況的發生,CMS收集器對先前收集所耗時間和代空間充滿所耗時間進行統計,并據此確定收集啟動時間。另外,當年長代的空間占用率超過啟動占用率(initiating occupancy)時,CMS收集器也將啟動一次收集。啟動占用率的值可以通過命令行選項“-XX:CMSInitiatingOccupancyFraction=n”進行設定,其中 n 表示年長代空間大小的百分比。缺省值為68。
總的來說,與并行收集器相比,CMS收集器(有時甚至顯著地)減少了年長代收集的停頓時間,而代價是略有增加的年幼代收集的停頓時間、吞吐量方面的一些損失和額外的堆空間需求。
3)增量模式
CMS收集器可以采用讓并發階段增量完成的模式運行。這種模式通過對并發階段的周期性暫停把處理能力交還給應用程序,以減少并發階段持續時間過長所帶來的不利影響。收集工作被分成許多小的時間塊,它們在年幼代收集的間歇期被調度。當應用程序既需要CMS收集器提供的低停頓時間,又只能在很少的CPU(比如說1到2個)上運行時,這個特性就相當有用。
4)何時使用CMS收集器?
如果應用程序需要更短的垃圾收集停頓時間并且能夠承擔在運行時和垃圾收集器共享處理器資源,那么就可以使用CMS收集器。(由于其并發性,在垃圾收集過程中CMS收集器將和應用程序搶奪CPU周期。)通常來說,具有較大的長壽數據集并且運行在2個或多個CPU上的應用程序,更容易受益于CMS收集器的使用。一個典型的例子就是Web服務器。對于任何需要低停頓時間的應用程序來說,CMS收集器都值得考慮。對于年長代尺寸適中并且運行在單一處理器上的交互式應用程序來說,使用CMS收集器同樣可能取得不錯的效果。
5)CMS收集器的選用
CMS收集器只能通過命令行選項“-XX:+UseConcMarkSweepGC”進行顯示的選用。如果希望CMS收集器在增量模式下運行,還需要通過命令行選項“-XX:+CMSIncrementalMode”啟用該模式。
九、收集器、堆尺寸和虛擬機的自動選擇
在J2SE 5.0中,根據應用程序所運行的硬件平臺和操作系統,垃圾收集器、堆尺寸和HotSpot虛擬機(客戶機或服務器)的缺省值被自動選定。這些自動的選擇不僅減少了對命令行選項的使用,而且還更好的滿足了不同類型應用程序的需要。
1)服務器級硬件(server-class machine,不使用“機器”這個詞是因為讀起來太拗口)的定義:
服務器級硬件必須同時滿足以下兩個條件:
①擁有2個或以上的物理處理器
②擁有2GB或以上的物理內存
該定義適用于所有平臺,32位Windows平臺除外。
2)服務器級硬件與非服務器級硬件下各項缺省值的比較:
機器類型 |
虛擬機 |
垃圾收集器 |
堆尺寸初始值-Xms |
堆尺寸最大值-Xmx |
服務器級硬件 |
服務器版 |
并行收集器 |
物理內存的1/64,不超過1GB |
物理內存的1/4,不超過1GB |
非服務器級硬件 |
客戶機版 |
串行收集器 |
4MB |
64MB |
注意:本節所涉及的堆尺寸指的是Java堆的大小,包括年幼代和年長代,但不包括永久代。
eBay 架構經驗
- Partition Everything 切分萬物
- Asynchrony Everywhere 處處異步
- Automate Everything 全部自動
- Remember Everything Fails 記錄失敗
- Embrace Inconsistency 親不同是謂大同
- Expect (R)evolution 預言演變
- Dependencies Matter 重視依賴
- Be Authoritative 獨斷專行
- Never Enough Data
淘寶架構經驗
- Partition Everything 切分萬物
- 適當放棄一致性
- 備份和隔離解決穩定性問題
- 分割和異步解決性能問題(類似 eBay 的 Asynchrony Everywhere)
- 自動化降低人力成本(類似 eBay 的 Automate Everything)
- 產品化管理
Flickr架構經驗
- 使得機器自動構建 (Teach machines to build themselves)
- 使得機器自監控(Teach machines to watch themselves)
- 使得機器自修復(Teach machines to fix themselves)
- 通過流程減少 MTTR (Reduce MTTR by streamlining)
架構的關注點是系統。其全名本來也是系統架構。它是系統級的主題。它當然也屬于系統設計過程的一個部分。只是與面向對象聚焦于業務領域不同,它聚焦于解決所有系統共同的問題,或者說與業務邏輯無關的問題。
上面所列出的技術,其實可以全部歸結為對以下技術的采用:
- 自動化
- 錯誤記錄
- 異步
- 接受不一致性即適當地放棄正確性
- 對系統進行適當的抽象定義(橫向與豎向。模塊與方面。數據分割。。。模塊,方面,分割的數據都是一種抽象。定義是為了管理。沒有定義就沒有管理。定義是管理的前提。要不然,“管理”什么?)
- 可進化性
- 面向用戶(即產品化。產品化指的是從產品的角度對產品進行包裝,,包括產品服務,錯誤,交互,UI等等)
- 隔離(管理依賴--剔除不必要的依賴,管理必要的依賴)
- 使得機器自監控(Teach machines to watch themselves)