2013年6月20日


版权信息: 可以任意转载, 转载时请务必以超链接形式标明文章原文出处, 即下面的声明.

原文出处:http://blog.chenlb.com/2009/06/java-classloader-architecture.html

jvm classLoader architecture:

  1. Bootstrap ClassLoader/启动类加载器 
    主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器 
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器 
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类) 
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。

类加载器的特性:

  1. 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
  2. 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。
classloader-architecture

classloader-architecture

classloader-class-diagram

classloader-class-diagram

类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。

自定义类加载器加载一个类的步骤

classloader-load-class

classloader-load-class

ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

  1. // 检查类是否已被装载过  
  2. Class c = findLoadedClass(name);  
  3. if (c == null ) {  
  4.      // 指定类未被装载过  
  5.      try {  
  6.          if (parent != null ) {  
  7.              // 如果父类加载器不为空, 则委派给父类加载  
  8.              c = parent.loadClass(name, false );  
  9.          } else {  
  10.              // 如果父类加载器为空, 则委派给启动类加载加载  
  11.              c = findBootstrapClass0(name);  
  12.          }  
  13.      } catch (ClassNotFoundException e) {  
  14.          // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其  
  15.          // 捕获, 并通过findClass方法, 由自身加载  
  16.          c = findClass(name);  
  17.      }  
  18. }  

线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader)。

  1. // Now create the class loader to use to launch the application  
  2. try {  
  3.     loader = AppClassLoader.getAppClassLoader(extcl);  
  4. catch (IOException e) {  
  5.     throw new InternalError(  
  6. "Could not create application class loader" );  
  7. }   
  8.   
  9. // Also set the context class loader for the primordial thread.  
  10. Thread.currentThread().setContextClassLoader(loader);  

以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择。

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。

为什么要使用这种双亲委托模式呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

java动态载入class的两种方式:

  1. implicit隐式,即利用实例化才载入的特性来动态载入class
  2. explicit显式方式,又分两种方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法

用Class.forName加载类

Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。

  1. public static Class forName(String className)  
  2.      throws ClassNotFoundException {  
  3.      return forName0(className, true , ClassLoader.getCallerClassLoader());  
  4. }   
  5.   
  6. /** Called after security checks have been made. */  
  7. private static native Class forName0(String name, boolean initialize,  
  8. ClassLoader loader)  
  9.      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类时不成功则抛出这个异常
posted @ 2013-06-20 10:25 陈睿 阅读(337) | 评论 (1)编辑 收藏

2012年7月3日

一:quartz简介
       OpenSymphony 的Quartz提供了一个比较完美的任务调度解决方案。
       Quartz 是个开源的作业调度框架,定时调度器,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
       Quartz中有两个基本概念:作业和触发器。作业是能够调度的可执行任务,触发器提供了对作业的调度

二:quartz spring配置详解
  •  为什么不适用java.util.Timer结合java.util.TimerTask 
        1.主要的原因,适用不方便,特别是制定具体的年月日时分的时间,而quartz使用类似linux上的cron配置,很方便的配置每隔时间执行触发。

        2.其次性能的原因,使用jdk自带的Timer不具备多线程,而quartz采用线程池,性能上比timer高出很多。


  •    详解quartz在spring里面的配置
    在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的一个很重要的原因
posted @ 2012-07-03 14:42 陈睿 阅读(3059) | 评论 (2)编辑 收藏

2012年3月30日

之前有接触过hadoop,但都比较浅显,对立面的东东不是很清楚!
打算后面在hadoop上花时间把里面的内容,好好学学,这篇博客将在后面陆续更新hadoop学习笔记。
posted @ 2012-03-30 10:14 陈睿 阅读(338) | 评论 (0)编辑 收藏

2012年3月12日

一:基本原理
    主要是要实现网络之间的通讯,网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络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应用级协议内容:
1、传输的标准格式是什么?
      是Java ObjectStream。
2、怎么样将请求转化为传输的流?
      基于Java串行化机制将请求的java object信息转化为流。
3、怎么接收和处理流?
      根据采用的协议启动相应的监听端口,当有流进入后基于Java串行化机制将流进行反序列化,并根据RMI协议获取到相应的处理对象信息,进行调用并处理,处理完毕后的结果同样基于java串行化机制进行返回。
4、传输协议是?
      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()方法,客户来询问注册。
posted @ 2012-03-12 09:57 陈睿 阅读(2269) | 评论 (2)编辑 收藏

2012年2月29日

一: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管理业务层代码的依赖关系,包括:淘宝,支付宝,阿里巴巴,百度等公司。
        
posted @ 2012-02-29 14:45 陈睿 阅读(1595) | 评论 (0)编辑 收藏

2012年2月27日

一: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思路的最重要的一个原因,也迎合了整个技术的一个发展方向,解耦一直贯穿于整个框架。
        
posted @ 2012-02-27 17:07 陈睿 阅读(1637) | 评论 (2)编辑 收藏

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 IBMJDK好坏主要取决于他们各自实现的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寄存器的内容总是指向下一条将被执行指令的饿&ldquo;地址&rdquo;,这里的&ldquo;地址&rdquo;可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。

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的比值81,这样eden+2*survivor=1/9

堆内存

-XX:SurvivorRatio=32意味着eden和一个survivor的比值是321,这样一个Survivor就占Young区的1/34.

-Xmn 参数设置了年轻代的大小

Ø Perm Generation

-XX:PermSize=16M -XX:MaxPermSize=64M

Thread Stack

-XX:Xss=128K

 


posted @ 2012-02-27 15:35 陈睿 阅读(497) | 评论 (1)编辑 收藏

2012年2月24日

数据库端性能非常低
  •     优化数据库服务器端的配置参数
  •     应用服务器端数据连接池的配置参数修改
  •     应用服务器端的sql审核,建立更好的索引以及修改不好的sql语句:关联表过多,查询的数据量过大,表设计不合理等
  •     应用服务器端拆解过大的表,分为多张表,甚至把一个数据库分为多个数据库
  •     数据库服务器端拆解为读/写分离,Master/Slave方式,一台写主机对应两台或则多台读的备用机器
应用服务器端
  •     访问压力过大,1台机器不能承受,该为多台机器,应用服务器配置为集群模式
    
    
posted @ 2012-02-24 16:17 陈睿 阅读(210) | 评论 (0)编辑 收藏

2012年2月21日

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
posted @ 2012-02-21 15:32 陈睿 阅读(1409) | 评论 (0)编辑 收藏
  1. HashSet概要
  •     采用HashMap存储,key直接存取值,value存储一个object
  •     存储的key值是唯一的
  •      HashSet中元素的顺序是随机的,包括添加(add())和输出都是无序的

    代码就不具体详解了,主要就是通过封装HashMap组成。

posted @ 2012-02-21 11:37 陈睿 阅读(1083) | 评论 (0)编辑 收藏
仅列出标题  下一页

导航

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜