版權信息: 可以任意轉載, 轉載時請務必以超鏈接形式標明文章原文出處, 即下面的聲明.
原文出處:http://blog.chenlb.com/2009/06/java-classloader-architecture.html
jvm classLoader architecture:
- Bootstrap ClassLoader/啟動類加載器
主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。 - Extension ClassLoader/擴展類加載器
主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。 - System ClassLoader/系統類加載器
主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。 - User Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件, 體現java動態實時類裝入特性。
類加載器的特性:
- 每個ClassLoader都維護了一份自己的名稱空間, 同一個名稱空間里不能出現兩個同名的類。
- 為了實現java安全沙箱模型頂層的類加載器安全機制, java默認采用了 " 雙親委派的加載鏈 " 結構。

classloader-architecture

classloader-class-diagram
類圖中, BootstrapClassLoader是一個單獨的java類, 其實在這里, 不應該叫他是一個java類。因為,它已經完全不用java實現了。它是在jvm啟動時, 就被構造起來的, 負責java平臺核心庫。
自定義類加載器加載一個類的步驟

classloader-load-class
ClassLoader 類加載邏輯分析, 以下邏輯是除 BootstrapClassLoader 外的類加載器加載流程:
- // 檢查類是否已被裝載過
- Class c = findLoadedClass(name);
- if (c == null ) {
- // 指定類未被裝載過
- try {
- if (parent != null ) {
- // 如果父類加載器不為空, 則委派給父類加載
- c = parent.loadClass(name, false );
- } else {
- // 如果父類加載器為空, 則委派給啟動類加載加載
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // 啟動類加載器或父類加載器拋出異常后, 當前類加載器將其
- // 捕獲, 并通過findClass方法, 由自身加載
- c = findClass(name);
- }
- }
線程上下文類加載器
java默認的線程上下文類加載器是 系統類加載器(AppClassLoader)。
- // Now create the class loader to use to launch the application
- try {
- loader = AppClassLoader.getAppClassLoader(extcl);
- } catch (IOException e) {
- throw new InternalError(
- "Could not create application class loader" );
- }
-
- // Also set the context class loader for the primordial thread.
- Thread.currentThread().setContextClassLoader(loader);
以上代碼摘自sun.misc.Launch的無參構造函數Launch()。
使用線程上下文類加載器, 可以在執行線程中, 拋棄雙親委派加載鏈模式, 使用線程上下文里的類加載器加載類.
典型的例子有, 通過線程上下文來加載第三方庫jndi實現, 而不依賴于雙親委派.
大部分java app服務器(jboss, tomcat..)也是采用contextClassLoader來處理web服務。
還有一些采用 hotswap 特性的框架, 也使用了線程上下文類加載器, 比如 seasar (full stack framework in japenese).
線程上下文從根本解決了一般應用不能違背雙親委派模式的問題.
使java類加載體系顯得更靈活.
隨著多核時代的來臨, 相信多線程開發將會越來越多地進入程序員的實際編碼過程中. 因此,
在編寫基礎設施時, 通過使用線程上下文來加載類, 應該是一個很好的選擇。
當然, 好東西都有利弊. 使用線程上下文加載類, 也要注意, 保證多根需要通信的線程間的類加載器應該是同一個,
防止因為不同的類加載器, 導致類型轉換異常(ClassCastException)。
為什么要使用這種雙親委托模式呢?
- 因為這樣可以避免重復加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
- 考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義類型,這樣會存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因為String已經在啟動時被加載,所以用戶自定義類是無法加載一個自定義的ClassLoader。
java動態載入class的兩種方式:
- implicit隱式,即利用實例化才載入的特性來動態載入class
- explicit顯式方式,又分兩種方式:
- java.lang.Class的forName()方法
- java.lang.ClassLoader的loadClass()方法
用Class.forName加載類
Class.forName使用的是被調用者的類加載器來加載類的。
這種特性, 證明了java類加載器中的名稱空間是唯一的, 不會相互干擾。
即在一般情況下, 保證同一個類中所關聯的其他類都是由當前類的類加載器所加載的。
- public static Class forName(String className)
- throws ClassNotFoundException {
- return forName0(className, true , ClassLoader.getCallerClassLoader());
- }
-
- /** Called after security checks have been made. */
- private static native Class forName0(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException;
上面中 ClassLoader.getCallerClassLoader 就是得到調用當前forName方法的類的類加載器
static塊在什么時候執行?
- 當調用forName(String)載入class時執行,如果調用ClassLoader.loadClass并不會執行.forName(String,false,ClassLoader)時也不會執行.
- 如果載入Class時沒有執行static塊則在第一次實例化時執行.比如new ,Class.newInstance()操作
- static塊僅執行一次
各個java類由哪些classLoader加載?
- java類可以通過實例.getClass.getClassLoader()得知
- 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()獲得實例)載入
- ClassLoader類由bootstrap loader載入
NoClassDefFoundError和ClassNotFoundException
- NoClassDefFoundError:當java源文件已編譯成.class文件,但是ClassLoader在運行期間在其搜尋路徑load某個類時,沒有找到.class文件則報這個錯
- ClassNotFoundException:試圖通過一個String變量來創建一個Class類時不成功則拋出這個異常
一:quartz簡介 OpenSymphony 的Quartz提供了一個比較完美的任務調度解決方案。 Quartz 是個開源的作業調度框架,定時調度器,為在 Java 應用程序中進行作業調度提供了簡單卻強大的機制。
Quartz中有兩個基本概念:作業和觸發器。作業是能夠調度的可執行任務,觸發器提供了對作業的調度
二:quartz spring配置詳解- 為什么不適用java.util.Timer結合java.util.TimerTask
1.主要的原因,適用不方便,特別是制定具體的年月日時分的時間,而quartz使用類似linux上的cron配置,很方便的配置每隔時間執行觸發。
2.其次性能的原因,使用jdk自帶的Timer不具備多線程,而quartz采用線程池,性能上比timer高出很多。
在spring里主要分為兩種使用方式:第一種,也是目前使用最多的方式,spring提供的MethodInvokingJobDetailFactoryBean代理類,通過雷利類直接調用任務類的某個函數;第二種,程序里實現quartz接口,quartz通過該接口進行調度。
主要講解通過spring提供的代理類MethodInvokingJobDetailFactoryBean 1.業務邏輯類:業務邏輯是獨立的,本身就與quartz解耦的,并沒有深入進去,這對業務來講是很好的一個方式。
public class TestJobTask{ /**
*業務邏輯處理
*/ public void service(){
/**業務邏輯*/ 


..
}
}
2.增加一個線程池 <!-- 線程執行器配置,用于任務注冊 --><bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="500" />
</bean>
3.定義業務邏輯類
<!-- 業務對象 --><bean id="testJobTask" class="com.mike.scheduling.TestJobTask" />
4.增加quartz調用業務邏輯
<!-- 調度業務 --><bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="testJobTask" />
<property name="targetMethod" value="service" />
</bean>
5.增加調用的觸發器,觸發的時間,有兩種方式:
第一種觸發時間,采用類似linux的cron,配置時間的表示發出豐富 <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="10 0/1 * * * ?" />
</bean>
Cron表達式“10 */1 * * * ?”意為:從10秒開始,每1分鐘執行一次 第二種,采用比較簡話的方式,申明延遲時間和間隔時間
<bean id="taskTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean"> <property name="jobDetail" ref="jobDetail" />
<property name="startDelay" value="10000" />
<property name="repeatInterval" value="60000" />
</bean>
延遲10秒啟動,然后每隔1分鐘執行一次 6.開始調用
<!-- 設置調度 --><bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
<property name="taskExecutor" ref="executor" />
</bean>
7.結束:啟動容器即可,已經將spring和quartz結合完畢。 Cron常用的表達式 "0 0 12 * * ?" 每天中午12點觸發"0 15 10 ? * *" 每天上午10:15觸發
"0 15 10 * * ?" 每天上午10:15觸發
"0 15 10 * * ? *" 每天上午10:15觸發
"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發
"0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鐘觸發
"0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鐘觸發
"0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
"0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鐘觸發
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發
"0 15 10 15 * ?" 每月15日上午10:15觸發
"0 15 10 L * ?" 每月最后一日的上午10:15觸發
"0 15 10 ? * 6L" 每月的最后一個星期五上午10:15觸發
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一個星期五上午10:15觸發
"0 15 10 ? * 6#3" 每月的第三個星期五上午10:15觸發
三:quartz原理
根據上面spring的配置,我們就比較清楚quartz的內部情況,下面我們主要詳解配置涉及到的每個點 1.我們先從最后一個步驟看起,
SchedulerFactoryBean ,scheduler的工廠實現,里面可以生產出對應的多個jobDetail和trigger,每個jobDetail對應trigger代表一個任務
Quartz的SchedulerFactory是標準的工廠類,不太適合在Spring環境下使用。此外,為了保證Scheduler能夠感知 Spring容器的生命周期,完成自動啟動和關閉的操作,必須讓Scheduler和Spring容器的生命周期相關聯。以便在Spring容器啟動后, Scheduler自動開始工作,而在Spring容器關閉前,自動關閉Scheduler。為此,Spring提供 SchedulerFactoryBean,這個FactoryBean大致擁有以下的功能: 1)以更具Bean風格的方式為Scheduler提供配置信息;
2)讓Scheduler和Spring容器的生命周期建立關聯,相生相息;
3)通過屬性配置部分或全部代替Quartz自身的配置文件。
2.jobDetail,表示一個可執行的業務調用
3.trigger:調度的時間計劃,什么時候,每隔多少時間可執行等時間計劃
4.ThreadPoolTaskExecutor,線程池,用來并行執行每個對應的job,提高效率,這也是上面提到不推薦使用jdk自身timer的一個很重要的原因
之前有接觸過hadoop,但都比較淺顯,對立面的東東不是很清楚!
打算后面在hadoop上花時間把里面的內容,好好學學,這篇博客將在后面陸續更新hadoop學習筆記。
一:基本原理 主要是要實現網絡之間的通訊,
網絡通信需要做的就是將流從一臺計算機傳輸到另外一臺計算機,基于傳輸協議和網絡IO來實現,其中傳輸協議比較出名的有http、 tcp、udp等等,http、tcp、udp都是在基于Socket概念上為某類應用場景而擴展出的傳輸協議,網絡IO,主要有bio、nio、aio 三種方式,所有的分布式應用通訊都基于這個原理而實現。
二:實踐
在分布式服務框架中,一個最基礎的問題就是遠程服務是怎么通訊的,在Java領域中有很多可實現遠程通訊的技術:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS
既然引入出了這么多技術,那我們就順道深入挖掘下去,了解每個技術框架背后的東西:
1.首先看RMI
RMI主要包含如下內容:
遠程服務的接口定義
·遠程服務接口的具體實現 ·樁(Stub)和框架(Skeleton)文件 ·一個運行遠程服務的服務器 ·一個RMI命名服務,它允許客戶端去發現這個遠程服務 ·類文件的提供者(一個HTTP或者FTP服務器) ·一個需要這個遠程服務的客戶端程序 來看下基于RMI的一次完整的遠程通信過程的原理:
1)客戶端發起請求,請求轉交至RMI客戶端的stub類;
2)stub類將請求的接口、方法、參數等信息進行序列化;
3)基于tcp/ip將序列化后的流傳輸至服務器端;
4)服務器端接收到流后轉發至相應的skelton類;
5)skelton類將請求的信息反序列化后調用實際的處理類;
6)處理類處理完畢后將結果返回給skelton類;
7)Skelton類將結果序列化,通過tcp/ip將流傳送給客戶端的stub;
8)stub在接收到流后反序列化,將反序列化后的Java Object返回給調用者。
RMI應用級協議內容:
基于Java串行化機制將請求的java object信息轉化為流。
根據采用的協議啟動相應的監聽端口,當有流進入后基于Java串行化機制將流進行反序列化,并根據RMI協議獲取到相應的處理對象信息,進行調用并處理,處理完畢后的結果同樣基于java串行化機制進行返回。
tcp/ip。
原理講了,開始實踐:
創建RMI程序的6個步驟:
1、定義一個遠程接口的接口,該接口中的每一個方法必須聲明它將產生一個RemoteException異常。
2、定義一個實現該接口的類。
3、使用RMIC程序生成遠程實現所需的殘根和框架。
4、創建一個服務器,用于發布2中寫好的類。
5. 創建一個客戶程序進行RMI調用。
6、啟動rmiRegistry并運行自己的遠程服務器和客戶程序
1)首先創建遠程接口:
/**
* 遠程接口
*
* @author mike
*
* @since 2012-3-14
*/
public interface Hello extends Remote {
/**
* 測試rmi
*
* @return hello
* @throws RemoteException
*/
public String hello()throws RemoteException;
}
2)創建接口實現
package com.gewara.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 遠程接口實現
*
* @author mike
*
* @since 2012-3-14
*/
public class HelloImpl extends UnicastRemoteObject implements Hello {
/**
* seria id
*/
private static final long serialVersionUID = -7931720891757437009L;
protected HelloImpl() throws RemoteException {
super();
}
/**
* hello實現
*
* @return hello world
* @throws RemoteException
*/
public String hello() throws RemoteException {
return "hello world";
}
}
3)創建服務器端
package com.gewara.rmi;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class Server {
private static final String RMI_URL="rmi://192.168.2.89:10009/server";
/**
* RMI Server
*/
public Server() {
try {
//創建遠程對象
Hello hello=new HelloImpl();
//啟動注冊表
LocateRegistry.createRegistry(10009);
//將名稱綁定到對象
Naming.bind(RMI_URL, hello);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param args
*/
public static void main(String[] args) {
new Server();
}
}
4)創建客服端
package com.gewara.rmi;
import java.rmi.Naming;
public class Client {
private static final String RMI_URL="rmi://192.168.2.89:10009/server";
/**
* @param args
*/
public static void main(String[] args) {
try {
String result=((Hello)Naming.lookup(RMI_URL)).hello();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5)先啟動服務器端,然后再啟動客戶端
顯示結果:hello world
由于涉及到的內容比較多,打算每一篇里講一個遠程通訊框架,繼續詳解RMI
三:詳解RMI內部原理
1. RMI基本結構:包含兩個獨立的程序,服務器和客戶端,服務器創建多個遠程對象,讓遠程對象能夠被引用,等待客戶端調用這些遠程對象的方法。客戶端從服務器獲取到一個或則多個遠程對象的引用,然后調用遠程對象方法,主要涉及到RMI接口、回調等技術。
2.RMI回調:服務器提供遠程對象引用供客戶端調用,客戶端主動調用服務器,如果服務器主動打算調用客戶端,這就叫回調。
3.命名遠程對象:客戶端通過一個命名或則一個查找服務找到遠程服務,遠程服務包含Java的命名和查找接口(Java Naming and Directory Interface)JNDI
RMI提供了一種服務:RMI注冊rmiregistry,默認端口:1099,主機提供遠程服務,接受服務,啟動注冊服務的命令:start rmiregistry
客戶端使用一個靜態類Naming到達RMI注冊處,通過方法lookup()方法,客戶來詢問注冊。
一:spring概要 簡單來說,Spring是一個輕量級的控制反轉(IoC)和面向切面(AOP)的容器框架。
◆
控制反轉——Spring通過一種稱作控制反轉(IoC)的技術促進了松耦合。當應用了IoC,一個對象依賴的其它對象會通過被動的方式傳遞進來,而不是這個對象自己創建或者查找依賴對象。你可以認為IoC與JNDI相反——不是對象從容器中查找依賴,而是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。
◆
面向切面——Spring提供了
面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用對象只實現它們應該做的——完成業務邏輯——僅此而已。它們并不負責(甚至是意識)其它的系統級關注點,例如日志或事務支持。
◆
容器——Spring包含并管理應用對象的配置和生命周期,在這個意義上它是一種容器,你可以配置你的每個bean如何被創建——基于一個可配置
原型(prototype),你的bean可以創建一個單獨的實例或者每次需要時都生成一個新的實例——以及它們是如何相互關聯的。然而,Spring不應該被混同于傳統的重量級的EJB容器,它們經常是龐大與笨重的,難以使用。
◆
框架——Spring可以將簡單的組件配置、組合成為復雜的應用。在Spring中,應用對象被聲明式地組合,典型地是在一個XML文件里。Spring也提供了很多基礎功能(事務管理、持久化框架集成等等),將應用邏輯的開發留給了你。
所有Spring的這些特征使你能夠編寫更干凈、更可管理、并且更易于測試的代碼。它們也為Spring中的各種模塊提供了基礎支持。
二:spring的整個生命周期 首先說一下spring的整個初始化過程,web應用中創建spring容器有兩種方式: 第一種:在web.xml里直接配置spring容器,servletcontextlistener
第二種:通過load-on-startup servlet實現。
主要就說一下第一種方式:
spring提供了ServletContextListener的實現類ContextLoaderListener,該類作為listener使用,在創建時自動查找WEB-INF目錄下的applicationContext.xml,該文件是默認查找的,如果只有一個就不需要配置初始化xml參數,如果需要配置,設置contextConfigLocation為application的xml文件即可。可以好好閱讀一下ContextLoaderListener的源代碼,就可以很清楚的知道spring的整個加載過程。
spring容器的初始化代碼如下:
/** * Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());//contextLoader初始化web應用容器
}
繼續分析initWebApplicationContext做了什么事情:
/**
* Initialize Spring's web application context for the given servlet context,
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//首先創建一個spring的父容器,類似根節點root容器,而且只能是一個,如果已經創建,拋出對應的異常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);//創建通過web.xml配置的父容器
具體里面的代碼是怎么實現的,就不在這里進行詳解了
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
this.context = createWebApplicationContext(servletContext, parent);//主要的創建過程都在改方法內,可以自己去看源代碼
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//把spring初始好的容器加載到servletcontext內,相當于servletcontext包含webapplicationcontext
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
看到這里基本已經清楚了整個spring容器的加載過程,如果還想了解更加深入,請查看我紅色標注的方法體。
其次再說一下spring的IOC和AOP使用的場景,由于原理大家都很清楚了,那就說一下它們使用到的地方:
IOC使用的場景:
管理bean的依賴關系,目前主流的電子商務網站基本都采用spring管理業務層代碼的依賴關系,包括:淘寶,支付寶,阿里巴巴,百度等公司。
一:struts2概要 以WebWork優秀設計思想為核心,吸收了struts1的部分優點。
二:struts2詳解 主要就是詳解struts2與struts1之間的區別,以及為什么要采用webwork重新設計新框架,以及吸收了struts1的哪部分優點。
首先將區別:- 最大的區別是與servlet成功解耦,不在依賴容器來初始化HttpServletRequest和HttpServletResponse
struts1里依賴的核心控制器為ActionServlet而struts2依賴ServletDispatcher,一個是servlet一個是filter,正是采用了filter才不至于和servlet耦合,所有的數據 都是通過攔截器來實現,如下圖顯示:

- web層表現層的豐富,struts2已經可以使用jsp、velocity、freemarker
- 線程模式方面:struts1的action是單例模式而且必須是線程安全或同步的,是struts2的action對每一個請求都產生一個新的實例,因此沒有線程安全問 題。
- 封裝請求參數:是struts1采用ActionForm封裝請求參數,都必須繼承ActionForm基類,而struts2通過bean的屬性封裝,大大降低了耦合。
- 類型轉換:struts1封裝的ActionForm都是String類型,采用Commons- Beanutils進行類型轉換,每個類一個轉換器;struts2采用OGNL進行類型轉 換,支持基本數據類型和封裝類型的自動轉換。
- 數據校驗:struts1在ActionForm中重寫validate方法;struts2直接重寫validate方法,直接在action里面重寫即可,不需要繼承任何基類,實際的調用順序是,validate()-->execute(),會在執行execute之前調用validate,也支持xwork校驗框架來校驗。
其次,講一下為什么要采用webwork來重新設計struts2
首先的從核心控制器談起,struts2的FilterDispatcher,這里我們知道是一個filter而不是一個servlet,講到這里很多人還不是很清楚web.xml里它們之間的聯系,先簡短講一下它們的加載順序,context-param(應用范圍的初始化參數)-->listener(監聽應用端的任何修改通知)-->filter(過濾)-->servlet。
filter在執行servlet之間就以及調用了,所以才有可能解脫完全依賴servlet的局面,那我們來看看這個filter做了什么事情:
/** * Process an action or handle a request a static resource.
* <p/>
* The filter tries to match the request to an action mapping.
* If mapping is found, the action processes is delegated to the dispatcher's serviceAction method.
* If action processing fails, doFilter will try to create an error page via the dispatcher.
* <p/>
* Otherwise, if the request is for a static resource,
* the resource is copied directly to the response, with the appropriate caching headers set.
* <p/>
* If the request does not match an action mapping, or a static resource page,
* then it passes through.
*
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
ServletContext servletContext = getServletContext();
String timerKey = "FilterDispatcher_doFilter: ";
try {
// FIXME: this should be refactored better to not duplicate work with the action invocation
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
ActionContext ctx = new ActionContext(stack.getContext());
ActionContext.setContext(ctx);
UtilTimerStack.push(timerKey);
request = prepareDispatcherAndWrapRequest(request, response);
ActionMapping mapping;
try {
mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
} catch (Exception ex) {
log.error("error getting ActionMapping", ex);
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
return;
}
if (mapping == null) {
// there is no action in this request, should we look for a static resource?
String resourcePath = RequestUtils.getServletPath(request);
if ("".equals(resourcePath) && null != request.getPathInfo()) {
resourcePath = request.getPathInfo();
}
if (staticResourceLoader.canHandle(resourcePath)) {
staticResourceLoader.findStaticResource(resourcePath, request, response);
} else {
// this is a normal request, let it pass through
chain.doFilter(request, response);
}
// The framework did its job here
return;
}
dispatcher.serviceAction(request, response, servletContext, mapping);//過濾用戶請求,攔截器執行,把對應的action請求轉到業務action執行 }
finally {
try {
ActionContextCleanUp.cleanUp(req);
} finally {
UtilTimerStack.pop(timerKey);
}
}
}
對應的action參數由攔截器獲取。
解耦servlet是struts2采用webwork思路的最重要的一個原因,也迎合了整個技術的一個發展方向,解耦一直貫穿于整個框架。
JVM specification對JVM內存的描述
首先我們來了解JVM specification中的JVM整體架構。如下圖:

主要包括兩個子系統和兩個組件: Class loader(類裝載器) 子系統,Execution engine(執行引擎) 子系統;Runtime data area (運行時數據區域)組件, Native interface(本地接口)組件。
Class loader子系統的作用 :根據給定的全限定名類名(如 java.lang.Object)來裝載class文件的內容到 Runtime data area中的method area(方法區域)。Javsa程序員可以extends java.lang.ClassLoader類來寫自己的Class loader。
Execution engine子系統的作用 :執行classes中的指令。任何JVM specification實現(JDK)的核心是Execution engine, 換句話說:Sun 的JDK 和IBM的JDK好壞主要取決于他們各自實現的Execution engine的好壞。每個運行中的線程都有一個Execution engine的實例。
Native interface組件 :與native libraries交互,是其它編程語言交互的接口。
Runtime data area 組件:這個組件就是JVM中的內存

Runtime data area 主要包括五個部分:Heap (堆), Method Area(方法區域), Java Stack(java的棧), Program Counter(程序計數器), Native method stack(本地方法棧)。Heap 和Method Area是被所有線程的共享使用的;而Java stack, Program counter 和Native method stack是以線程為粒度的,每個線程獨自擁有。
Heap
Java程序在運行時創建的所有類實或數組都放在同一個堆中。而一個Java虛擬實例中只存在一個堆空間,因此所有線程都將共享這個堆。每一個java程序獨占一個JVM實例,因而每個java程序都有它自己的堆空間,它們不會彼此干擾。但是同一java程序的多個線程都共享著同一個堆空間,就得考慮多線程訪問對象(堆數據)的同步問題。 (這里可能出現的異常java.lang.OutOfMemoryError: Java heap space)
JVM堆一般又可以分為以下三部分:

Ø Perm
Perm代主要保存class,method,filed對象,這部門的空間一般不會溢出,除非一次性加載了很多的類,不過在涉及到熱部署的應用服務器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署后,類的class沒有被卸載掉,這樣就造成了大量的class對象保存在了perm中,這種情況下,一般重新啟動應用服務器可以解決問題。
Ø Tenured
Tenured區主要保存生命周期長的對象,一般是一些老的對象,當一些對象在Young復制轉移一定的次數以后,對象就會被轉移到Tenured區,一般如果系統中用了application級別的緩存,緩存中的對象往往會被轉移到這一區間。
Ø Young
Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時復制對象用,在Young區間變滿的時候,minor GC就會將存活的對象移到空閑的Survivor區間中,根據JVM的策略,在經過幾次垃圾收集后,任然存活于Survivor的對象將被移動到Tenured區間。
Method area
在Java虛擬機中,被裝載的class的信息存儲在Method area的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的
class文件,然后讀入這個class文件內容并把它傳輸到虛擬機中。緊接著虛擬機提取其中的類型信息,并將這些信息存儲到方法區。該類型中的類(靜態)變量同樣也存儲在方法區中。與Heap 一樣,method area是多線程共享的,因此要考慮多線程訪問的同步問題。比如,假設同時兩個線程都企圖訪問一個名為Lava的類,而這個類還沒有內裝載入虛擬機,那么,這時應該只有一個線程去裝載它,而另一個線程則只能等待。 (這里可能出現的異常java.lang.OutOfMemoryError: PermGen full)
Java stack
Java stack以幀為單位保存線程的運行狀態。虛擬機只會直接對Java stack執行兩種操作:以幀為單位的壓棧或出棧。每當線程調用一個方法的時候,就對當前狀態作為一個幀保存到
java stack中(壓棧);當一個方法調用返回時,從java stack彈出一個幀(出棧)。棧的大小是有一定的限制,這個可能出現StackOverFlow問題。 下面的程序可以說明這個問題。
public class TestStackOverFlow {
public static void main(String[] args) {
Recursive r = new Recursive();
r.doit(10000);
// Exception in thread "main" java.lang.StackOverflowError
}
}
class Recursive {
public int doit(int t) {
if (t <= 1) {
return 1;
}
return t + doit(t - 1);
}
}
Program counter
每個運行中的Java程序,每一個線程都有它自己的PC寄存器,也是該線程啟動時創建的。PC寄存器的內容總是指向下一條將被執行指令的餓“地址”,這里的“地址”可以是一個本地指針,也可以是在方法區中相對應于該方法起始指令的偏移量。
Native method stack
對于一個運行中的Java程序而言,它還能會用到一些跟本地方法相關的數據區。當某個線程調用一個本地方法時,它就進入了一個全新的并且不再受虛擬機限制的世界。本地方法可以通過本地方法接口來訪問虛擬機的運行時數據區,不止與此,它還可以做任何它想做的事情。比如,可以調用寄存器,或在操作系統中分配內存等。總之,本地方法具有和JVM相同的能力和權限。 (這里出現JVM無法控制的內存溢出問題native heap OutOfMemory )
JVM提供了相應的參數來對內存大小進行配置。

正如上面描述,JVM中堆被分為了3個大的區間,同時JVM也提供了一些選項對Young,Tenured的大小進行控制。
Ø Total Heap
-Xms :指定了JVM初始啟動以后初始化內存
-Xmx:指定JVM堆得最大內存,在JVM啟動以后,會分配-Xmx參數指定大小的內存給JVM,但是不一定全部使用,JVM會根據-Xms參數來調節真正用于JVM的內存
-Xmx -Xms之差就是三個Virtual空間的大小
Ø Young Generation
-XX:NewRatio=8意味著tenured 和 young的比值8:1,這樣eden+2*survivor=1/9
堆內存
-XX:SurvivorRatio=32意味著eden和一個survivor的比值是32:1,這樣一個Survivor就占Young區的1/34.
-Xmn 參數設置了年輕代的大小
Ø Perm Generation
-XX:PermSize=16M -XX:MaxPermSize=64M
Thread Stack
-XX:Xss=128K
1. 多線程概念:
線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬于某個進程,進程中的多個線程共享進程的內存。
- 多線程的實現方式和啟動
- 多線程是依靠什么方式解決資源競爭
- 多線程的各種狀態以及優先級
- 多線程的暫停方式
2. 多線程詳解 1)多線程的實現方式和啟動:- 繼承Thread和是實現Runnable接口,重寫run方法
- 啟動只有一種方式:通過start方法,虛擬機會調用run方法
2) 多線程依靠什么解決資源競爭- 鎖機制:分為對象鎖和類鎖,在多個線程調用的情況,每個對象鎖都是唯一的,只有獲取了鎖才能調用synchronized方法
- synchronize同步:分為同步方法和同步方法塊
- 什么時候獲取鎖:每次調用到synchronize方法,這個時候去獲取鎖資源,如果線程獲取到鎖則別的線程只有等到同步方法介紹后,釋放鎖后,別的線程 才能繼續使用
3)線程的幾種狀態- 主要分為:新狀態(還沒有調用start方法),可執行狀態(調用start方法),阻塞狀態,死亡狀態
默認優先級為normal(5),優先級數值在1-10之間
4) 多線程的暫停方式- sleep:睡眠單位為毫秒
- wait,waitAll,notify,notifyAll,wait等待,只有通過wait或者waitAll喚醒
- yield:cpu暫時停用
- join