﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-zerolurker-文章分类-工作日志</title><link>http://www.blogjava.net/zerolurker/category/19508.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:46:48 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:46:48 GMT</pubDate><ttl>60</ttl><item><title>系统优化</title><link>http://www.blogjava.net/zerolurker/articles/96449.html</link><dc:creator>lurker</dc:creator><author>lurker</author><pubDate>Mon, 29 Jan 2007 02:40:00 GMT</pubDate><guid>http://www.blogjava.net/zerolurker/articles/96449.html</guid><wfw:comment>http://www.blogjava.net/zerolurker/comments/96449.html</wfw:comment><comments>http://www.blogjava.net/zerolurker/articles/96449.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zerolurker/comments/commentRss/96449.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zerolurker/services/trackbacks/96449.html</trackback:ping><description><![CDATA[       上周解决了一个一直困扰我们的问题，就是待办任务查询慢，服务器很容易down机，但视察了好久都没有结果，特别是底层数据库操作方法--hibernate，最后发现结果是一个关联查询的数据库表没有建立索引，而这张表数据已经达到十多万条，因为这张表没有直接用到只是关联查询的时候才偶尔用到，所以开始建索引的时候就没有给这张表建索引，没想到这张表的数据增长的这么快，导致每当关联查询时就会操时导致数据回滚，服务器down机。最后给这张表加了索引，这个速度提升了一个等级。<br />        系统分析往往在不起眼的地方产生难以想象的后果。这种跟踪问题很困难，有时候会无从下手。<img src ="http://www.blogjava.net/zerolurker/aggbug/96449.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zerolurker/" target="_blank">lurker</a> 2007-01-29 10:40 <a href="http://www.blogjava.net/zerolurker/articles/96449.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java类加载内幕</title><link>http://www.blogjava.net/zerolurker/articles/95915.html</link><dc:creator>lurker</dc:creator><author>lurker</author><pubDate>Thu, 25 Jan 2007 06:04:00 GMT</pubDate><guid>http://www.blogjava.net/zerolurker/articles/95915.html</guid><wfw:comment>http://www.blogjava.net/zerolurker/comments/95915.html</wfw:comment><comments>http://www.blogjava.net/zerolurker/articles/95915.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zerolurker/comments/commentRss/95915.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zerolurker/services/trackbacks/95915.html</trackback:ping><description><![CDATA[类加载是java语言提供的最强大的机制之一。尽管类加载并不是讨论的热点话题，但所有的编程人员都应该了解其工作机制，明白如何做才能让其满足我们的需要。这能有效节省我们的编码时间，从不断调试ClassNotFoundException, ClassCastException的工作中解脱出来。
<p>　　这篇文章从基础讲起，比如代码与数据的不同之处是什么，他们是如何构成一个实例或对象的。然后深入探讨java虚拟机(</p>JVM)是如何利用类加载器读取代码，以及java中类加载器的主要类型。接着用一个类加载的基本算法看一下类加载器如何加载一个内部类。本文的下一节演示一段代码来说明扩展和开发属于自己的类加载器的必要性。紧接着解释如何使用定制的类加载器来完成一个一般意义上的任务，使其可以加载任意远端客户的代码，在JVM中定义，实例化并执行它。本文包括了J2EE关于类加载的规范——事实上这已经成为了J2EE的标准之一。 
<p>　　<strong>类与数据</strong></p><p>　　一个类代表要执行的代码，而数据则表示其相关状态。状态时常改变，而代码则不会。当我们将一个特定的状态与一个类相对应起来，也就意味着将一个类事例化。尽管相同的类对应的实例其状态千差万别，但其本质都对应着同一段代码。在JAVA中，一个类通常有着一个.class文件，但也有例外。在JAVA的运行时环境中(Java runtime)，每一个类都有一个以第一类(first-class)的Java对象所表现出现的代码，其是java.lang.Class的实例。我们编译一个JAVA文件，编译器都会嵌入一个public, static, final修饰的类型为java.lang.Class，名称为class的域变量在其字节码文件中。因为使用了public修饰，我们可以采用如下的形式对其访问:</p><p>　　java.lang.Class klass = Myclass.class;</p><p>　　一旦一个类被载入JVM中，同一个类就不会被再次载入了(切记，同一个类)。这里存在一个问题就是什么是“同一个类”?正如一个对象有一个具体的状态，即标识，一个对象始终和其代码(类)相关联。同理，载入JVM的类也有一个具体的标识，我们接下来看。</p><p>　　在JAVA中，一个类用其完全匹配类名(fully qualified class name)作为标识，这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此，如果一个名为Pg的包中，有一个名为Cl的类，被类加载器KlassLoader的一个实例kl1加载，Cl的实例，即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的，被它们所加载的类也因此完全不同，互不兼容的。那么在JVM中到底有多少种类加载器的实例?下一节我们揭示答案。</p><p>　　<strong>类加载器</strong></p><p>　　在JVM中，每一个类都被java.lang.ClassLoader的一些实例来加载.类ClassLoader是在包中java.lang里，开发者可以自由地继承它并添加自己的功能来加载类。</p><p>　　无论何时我们键入java MyMainClass来开始运行一个新的JVM，“引导类加载器(bootstrap class <a class="bluekey" href="http://www.yesky.com/key/3465/593465.html" target="_blank"><font color="#666666">loader</font></a>)”负责将一些关键的Java类，如java.lang.Object和其他一些运行时代码先加载进内存中。运行时的类在JRE\lib\rt.jar包文件中。因为这属于系统底层执行动作，我们无法在JAVA文档中找到引导类加载器的工作细节。基于同样的原因，引导类加载器的行为在各JVM之间也是大相径庭。</p><p>　　同理，如果我们按照如下方式:</p><p>　　log(java.lang.String.class.getClassLoader());</p><p>　　来获取java的核心运行时类的加载器，就会得到null。</p><p>　　接下来介绍java的扩展类加载器。扩展库提供比java运行代码更多的特性，我们可以把扩展库保存在由java.ext.dirs属性提供的路径中。</p><p>　　(编辑注:java.ext.dirs属性指的是系统属性下的一个key，所有的系统属性可以通过System.getProperties()方法获得。在编者的系统中，java.ext.dirs的value是” C:\Program Files\Java\jdk1.5.0_04\jre\lib\ext”。下面将要谈到的如java.class.path也同属系统属性的一个key。)</p><p>　　类ExtClassLoader专门用来加载所有java.ext.dirs下的.jar文件。开发者可以通过把自己的.jar文件或库文件加入到扩展目录的classpath，使其可以被扩展类加载器读取。</p><p>　　从开发者的角度，第三种同样也是最重要的一种类加载器是AppClassLoader。这种类加载器用来读取所有的对应在java.class.path系统属性的路径下的类。</p><p>　　Sun的java指南中，文章“理解扩展类加载”(Understanding Extension Class Loading)对以上三个类加载器路径有更详尽的解释，这是其他几个JDK中的类加载器</p><p><font face="Verdana">　　●java.net.URLClassLoader<br />　　●java.security.SecureClassLoader<br />　　●java.rmi.server.RMIClassLoader<br />　　●sun.applet.AppletClassLoader</font></p><p>　　java.lang.Thread，包含了public ClassLoader getContextClassLoader()方法，这一方法返回针对一具体线程的上下文环境类加载器。此类加载器由线程的创建者提供，以供此线程中运行的代码在需要加载类或资源时使用。如果此加载器未被建立，缺省是其父线程的上下文类加载器。原始的类加载器一般由读取应用程序的类加载器建立。</p><p>　　<strong>类加载器如何工作?</strong></p><p>　　除了引导类加载器，所有的类加载器都有一个父类加载器，不仅如此，所有的类加载器也都是java.lang.ClassLoader类型。以上两种类加载器是不同的，而且对于开发者自订制的类加载器的正常运行也至关重要。最重要的方面是正确设置父类加载器。任何类加载器，其父类加载器是加载该类加载器的类加载器实例。(记住，类加载器本身也是一个类!)</p><p>　　使用loadClass()方法可以从类加载器中获得该类。我们可以通过java.lang.ClassLoader的源代码来了解该方法工作的细节，如下:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">protected synchronized Class&lt;?&gt; loadClass<br />    (String name, boolean resolve)<br />    throws ClassNotFoundException{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    // First check if the class is already loaded<br />    Class c = findLoadedClass(name);<br />    if (c == null) {<br />        try {<br />            if (parent != null) {<br />                c = parent.loadClass(name, false);<br />            } else {<br />                c = findBootstrapClass0(name);<br />            }<br />        } catch (ClassNotFoundException e) {<br />            // If still not found, then invoke<br />            // findClass to find the class.<br />            c = findClass(name);<br />        }<br />    }<br />    if (resolve) {<br />            resolveClass(c);<br />    }<br />    return c;<br />}</font></p><p>　　我们可以使用ClassLoader的两种构造方法来设置父类加载器:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">public class MyClassLoader extends ClassLoader{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public MyClassLoader(){<br />        super(MyClassLoader.class.getClassLoader());<br />    }<br />}</font></p><p>　　或</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">public class MyClassLoader extends ClassLoader{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public MyClassLoader(){<br />        super(getClass().getClassLoader());<br />    }<br />}</font></p><p>　　第一种方式较为常用，因为通常不建议在构造方法里调用getClass()方法，因为对象的初始化只是在构造方法的出口处才完全完成。因此，如果父类加载器被正确建立，当要示从一个类加载器的实例获得一个类时，如果它不能找到这个类，它应该首先去访问其父类。如果父类不能找到它(即其父类也不能找不这个类，等等)，而且如果findBootstrapClass0()方法也失败了，则调用findClass()方法。findClass()方法的缺省实现会抛出ClassNotFoundException，当它们继承java.lang.ClassLoader来订制类加载器时开发者需要实现这个方法。findClass()的缺省实现方式如下:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    protected Class&lt;?&gt; findClass(String name)<br />        throws ClassNotFoundException {<br />        throw new ClassNotFoundException(name);<br />    }</font></p><p>　　在findClass()方法内部，类加载器需要获取任意来源的字节码。来源可以是文件系统，URL，数据库，可以产生字节码的另一个应用程序，及其他类似的可以产生java规范的字节码的来源。你甚至可以使用BCEL (Byte Code Engineering Library:字节码工程库)，它提供了运行时创建类的捷径。BCEL已经被成功地使用在以下方面:编译器，优化器，混淆器，代码产生器及其他分析工具。一旦字节码被检索，此方法就会调用defineClass()方法，此行为对不同的类加载实例是有差异的。因此，如果两个类加载实例从同一个来源定义一个类，所定义的结果是不同的。</p><p>　　JAVA语言规范(Java language specification)详细解释了JAVA执行引擎中的类或接口的加载(loading)，链接(linking)或初始化(initialization)过程。</p><p>　　图一显示了一个主类称为MyMainClass的应用程序。依照之前的阐述，MyMainClass.class会被AppClassLoader加载。 MyMainClass创建了两个类加载器的实例:CustomClassLoader1 和 CustomClassLoader2,他们可以从某数据源(比如网络)获取名为Target的字节码。这表示类Target的类定义不在应用程序类路径或扩展类路径。在这种情况下，如果MyMainClass想要用自定义的类加载器加载Target类，CustomClassLoader1和CustomClassLoader2会分别独立地加载并定义Target.class类。这在java中有重要的意义。如果Target类有一些静态的初始化代码，并且假设我们只希望这些代码在JVM中只执行一次，而这些代码在我们目前的步骤中会执行两次——分别被不同的CustomClassLoaders加载并执行。如果类Target被两个CustomClassLoaders加载并创建两个实例Target1和Target2，如图一显示，它们不是类型兼容的。换句话说，在JVM中无法执行以下代码:</p><p>　　Target target3 = (Target) target2;</p><p>　　以上代码会抛出一个ClassCastException。这是因为JVM把他们视为分别不同的类，因为他们被不同的类加载器所定义。这种情况当我们不是使用两个不同的类加载器CustomClassLoader1 和 CustomClassLoader2，而是使用同一个类加载器CustomClassLoader的不同实例时，也会出现同样的错误。这些会在本文后边用具体代码说明。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/2097hf979897.jpg" border="0" /></p><p>　　图1. 在同一个JVM中多个类加载器加载同一个目标类</p><p>　　关于类加载、定义和链接的更多解释，请参考Andreas Schaefer的"Inside Class Loaders."</p><p>　　<strong>为什么我们需要我们自己的类加载器</strong></p><p>　　原因之一为开发者写自己的类加载器来控制JVM中的类加载行为，java中的类靠其包名和类名来标识，对于实现了java.io.Serializable接口的类，serialVersionUID扮演了一个标识类版本的重要角色。这个唯一标识是一个类名、接口名、成员方法及属性等组成的一个64位的哈希字段，而且也没有其他快捷的方式来标识一个类的版本。严格说来，如果以上的都匹配，那么则属于同一个类。</p><p>　　但是让我们思考如下情况:我们需要开发一个通用的执行引擎。可以执行实现某一特定接口的任何任务。当任务被提交到这个引擎，首先需要加载这个任务的代码。假设不同的客户对此引擎提交了不同的任务，凑巧，这些所有的任务都有一个相同的类名和包名。现在面临的问题就是这个引擎是否可以针对不同的用户所提交的信息而做出不同的反应。这一情况在下文的参考一节有可供下载的代码样例，samepath 和 differentversions，这两个目录分别演示了这一概念。</p><p>　　图2 显示了文件目录结构，有三个子目录samepath, differentversions, 和 differentversionspush，里边是例子:</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/xw33i9xt766e.jpg" border="0" /></p><p>　　图2. 文件夹结构组织示例</p><p>　　在samepath 中，类version.Version保存在v1和v2两个子目录里，两个类具有同样的类名和包名，唯一不同的是下边这行:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public void fx(){<br />        log("this = " + this + "; Version.fx(1).");<br />    }</font></p><p>　　V1中，日志记录中有Version.fx(1)，而在v2中则是Version.fx(2)。把这个两个存在细微不同的类放在一个classpath下，然后运行Test类:</p><p>　　set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2</p><p>　　%JAVA_HOME%\bin\java Test</p><p>　　图3显示了控制台输出。我们可以看到对应着Version.fx(1)的代码被执行了，因为类加载器在classpath首先看到此版本的代码。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/047utg4n29dt.jpg" border="0" /></p><p>　　图3. 在类路径中samepath测试排在最前面的version 1</p><p>　　再次运行，类路径做如下微小改动。</p><p>　　set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1</p><p>　　%JAVA_HOME%\bin\java Test</p><p>　　控制台的输出变为图4。对应着Version.fx(2)的代码被加载，因为类加载器在classpath中首先找到它的路径。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/wply7f5z7u13.jpg" border="0" /></p><p>　　图4. 在类路径中samepath测试排在最前面的version 2</p><p>　　根据以上例子可以很明显地看出，类加载器加载在类路径中被首先找到的元素。如果我们在v1和v2中删除了version.Version，做一个非version.Version形式的.jar文件，如myextension.jar，把它放到对应java.ext.dirs的路径下，再次执行后看到version.Version不再被AppClassLoader加载，而是被扩展类加载器加载。如图5所示。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/6ybfc43l7lt2.jpg" border="0" /></p><p>　　图5. AppClassLoader及ExtClassLoader</p><p>　　继续这个例子，文件夹differentversions包含了一个RMI执行引擎，客户端可以提供给执行引擎任何实现了common.TaskIntf接口的任务。子文件夹client1 和 client2包含了类client.TaskImpl有个细微不同的两个版本。两个类的区别在以下几行:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    static{<br />        log("client.TaskImpl.class.getClassLoader<br />        (v1) : " + TaskImpl.class.getClassLoader());<br />    }</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public void execute(){<br />        log("this = " + this + "; execute(1)");<br />    }</font></p><p>　　在client1和client2里分别有getClassLoader(v1) 与 execute(1)和getClassLoader(v2) 与 execute(2)的的log语句。并且，在开始执行引擎RMI服务器的代码中，我们随意地将client2的任务实现放在类路径的前面。</p><p>　　CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;</p><p>　　%CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1</p><p>　　%JAVA_HOME%\bin\java server.Server</p><p>　　如图6，7，8的屏幕截图，在客户端VM，各自的client.TaskImpl类被加载、实例化，并发送到服务端的VM来执行。从服务端的控制台，可以明显看到client.TaskImpl代码只被服务端的VM执行一次，这个单一的代码版本在服务端多次生成了许多实例，并执行任务。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/r1c12i2ia7o3.jpg" border="0" /></p><p>　　图6. 执行引擎服务器控制台</p><p>　　图6显示了服务端的控制台，加载并执行两个不同的客户端的请求，如图7，8所示。需要注意的是，代码只被加载了一次(从静态初始化块的日志中也可以明显看出)，但对于客户端的调用这个方法被执行了两次。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/siv7a6p52k6z.jpg" border="0" /></p><p>　　图7. 执行引擎客户端 1控制台</p><p>　　图7中，客户端VM加载了含有client.TaskImpl.class.getClassLoader(v1)的日志内容的类TaskImpl的代码，并提供给服务端的执行引擎。图8的客户端VM加载了另一个TaskImpl的代码，并发送给服务端。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/4j7kax69v4x5.jpg" border="0" /></p><p>　　图8. 执行引擎客户端 2控制台</p><p>　　在客户端的VM中，类client.TaskImpl被分别加载，初始化，并发送到服务端执行。图6还揭示了client.TaskImpl的代码只在服务端的VM中加载了一次，但这“唯一的一次”却在服务端创造了许多实例并执行。或许客户端1该不高兴了因为并不是它的client.TaskImpl(v1)的方法调用被服务端执行了，而是其他的一些代码。如何解决这一问题?答案就是实现定制的类加载器。</p><p>　　<strong>定制类加载器</strong></p><p>　　要较好地控制类的加载，就要实现定制的类加载器。所有自定义的类加载器都应继承自java.lang.ClassLoader。而且在构造方法中，我们也应该设置父类加载器。然后重写findClass()方法。differentversionspush文件夹包含了一个叫做FileSystemClassLoader的自订制的类加载器。其结构如图9所示。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/22cve6owje59.jpg" border="0" /></p><p>　　图9. 定制类加载器关系</p><p>　　以下是在common.FileSystemClassLoader实现的主方法:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">public byte[] findClassBytes(String className){</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        try{<br />            String pathName = currentRoot +<br />                File.separatorChar + className.<br />                replace('.', File.separatorChar)<br />                + ".class";<br />            FileInputStream inFile = new<br />                FileInputStream(pathName);<br />            byte[] classBytes = new<br />                byte[inFile.available()];<br />            inFile.read(classBytes);<br />            return classBytes;<br />        }<br />        catch (java.io.IOException ioEx){<br />            return null;<br />        }<br />    }</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public Class findClass(String name)throws<br />        ClassNotFoundException{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        byte[] classBytes = findClassBytes(name);<br />        if (classBytes==null){<br />            throw new ClassNotFoundException();<br />        }<br />        else{<br />            return defineClass(name, classBytes,<br />                0, classBytes.length);<br />        }<br />    }</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public Class findClass(String name, byte[]<br />        classBytes)throws ClassNotFoundException{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        if (classBytes==null){<br />            throw new ClassNotFoundException(<br />                "(classBytes==null)");<br />        }<br />        else{<br />            return defineClass(name, classBytes,<br />                0, classBytes.length);<br />        }<br />    }</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public void execute(String codeName,<br />        byte[] code){</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        Class klass = null;<br />        try{<br />            klass = findClass(codeName, code);<br />            TaskIntf task = (TaskIntf)<br />                klass.newInstance();<br />            task.execute();<br />        }<br />        catch(Exception exception){<br />            exception.printStackTrace();<br />        }<br />    }</font></p><p>　　这个类供客户端把client.TaskImpl(v1)转换成字节数组，之后此字节数组被发送到RMI服务端。在服务端，一个同样的类用来把字节数组的内容转换回代码。客户端代码如下:</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">public class Client{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    public static void main (String[] args){</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        try{<br />            byte[] code = getClassDefinition<br />                ("client.TaskImpl");<br />            serverIntf.execute("client.TaskImpl",<br />                code);<br />            }<br />            catch(RemoteException remoteException){<br />                remoteException.printStackTrace();<br />            }<br />        }</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">    private static byte[] getClassDefinition<br />        (String codeName){<br />        String userDir = System.getProperties().<br />            getProperty("BytePath");<br />        FileSystemClassLoader fscl1 = null;</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        try{<br />            fscl1 = new FileSystemClassLoader<br />                (userDir);<br />        }<br />        catch(FileNotFoundException<br />            fileNotFoundException){<br />            fileNotFoundException.printStackTrace();<br />        }<br />        return fscl1.findClassBytes(codeName);<br />    }<br />}</font></p><p>　　在执行引擎中，从客户端收到的代码被送到定制的类加载器中。定制的类加载器把其从字节数组定义成类，实例化并执行。需要指出的是，对每一个客户请求，我们用类FileSystemClassLoader的不同实例来定义客户端提交的client.TaskImpl。而且，client.TaskImpl并不在服务端的类路径中。这也就意味着当我们在FileSystemClassLoader调用findClass()方法时，findClass()调用内在的defineClass()方法。类client.TaskImpl被特定的类加载器实例所定义。因此，当FileSystemClassLoader的一个新的实例被使用，类又被重新定义为字节数组。因此，对每个客户端请求类client.TaskImpl被多次定义，我们就可以在相同执行引擎JVM中执行不同的client.TaskImpl的代码。</p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">public void execute(String codeName, byte[] code)throws RemoteException{</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        FileSystemClassLoader fileSystemClassLoader = null;</font></p><p><font style="BACKGROUND-COLOR: #dddddd" face="Verdana">        try{<br />            fileSystemClassLoader = new FileSystemClassLoader();<br />            fileSystemClassLoader.execute(codeName, code);<br />        }<br />        catch(Exception exception){<br />            throw new RemoteException(exception.getMessage());<br />        }<br />    }</font></p><p>　　示例在differentversionspush文件夹下。服务端和客户端的控制台界面分别如图10，11，12所示:</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/sth3br076g80.jpg" border="0" /></p><p>　　图10. 定制类加载器执行引擎</p><p>　　图10显示的是定制的类加载器控制台。我们可以看到client.TaskImpl的代码被多次加载。实际上针对每一个客户端，类都被加载并初始化。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/5975e618h5l8.jpg" border="0" /></p><p>　　图11. 定制类加载器，客户端1</p><p>　　图11中，含有client.TaskImpl.class.getClassLoader(v1)的日志记录的类TaskImpl的代码被客户端的VM加载，然后送到服务端。图12 另一个客户端把包含有client.TaskImpl.class.getClassLoader(v1)的类代码加载并送往服务端。</p><p><img src="http://searchwebservices.techtarget.com.cn/imagelist/05/10/t5690273m6d9.jpg" border="0" /></p><p>　　图12. 定制类加载器，客户端1</p><p>　　这段代码演示了我们如何利用不同的类加载器实例来在同一个VM上执行不同版本的代码。</p><p>　　<strong>J2EE的类加载器</strong></p><p>　　J2EE的服务器倾向于以一定间隔频率，丢弃原有的类并重新载入新的类。在某些情况下会这样执行，而有些情况则不。同样，对于一个web服务器如果要丢弃一个servlet实例，可能是服务器管理员的手动操作，也可能是此实例长时间未相应。当一个JSP页面被首次请求，容器会把此JSP页面翻译成一个具有特定形式的servlet代码。一旦servlet代码被创建，容器就会把这个servlet翻译成class文件等待被使用。对于提交给容器的每次请求，容器都会首先检查这个JSP文件是否刚被修改过。是的话就重新翻译此文件，这可以确保每次的请求都是及时更新的。企业级的部署方案以.ear, .war, .rar等形式的文件，同样需要重复加载，可能是随意的也可能是依照某种配置方案定期执行。对所有的这些情况——类的加载、卸载、重新加载……全部都是建立在我们控制应用服务器的类加载机制的基础上的。实现这些需要扩展的类加载器，它可以执行由其自身所定义的类。Brett Peterson已经在他的文章 Understanding J2EE Application Server Class Loading Architectures给出了J2EE应用服务器的类加载方案的详细说明，详见网站TheServerSide.com。</p><p>　　<strong>结要</strong></p><p>　　本文探讨了类载入到虚拟机是如何进行唯一标识的，以及类如果存在同样的类名和包名时所产生的问题。因为没有一个直接可用的类版本管理机制，所以如果我们要按自己的意愿来加载类时，需要自己订制类加载器来扩展其行为。我们可以利用许多J2EE服务器所提供的“热部署”功能来重新加载一个新版本的类，而不改动服务器的VM。即使不涉及应用服务器，我们也可以利用定制类加载器来控制java应用程序载入类时的具体行为。Ted Neward的书Server-Based Java Programming中详细阐述java的类加载，J2EE的API以及使用他们的最佳途径。<br /><br /><br />原文：<a href="http://searchwebservices.techtarget.com.cn/tips/362/2158862.shtml"><font color="#666666">http://searchwebservices.techtarget.com.cn/tips/362/2158862.shtml</font></a></p><img src ="http://www.blogjava.net/zerolurker/aggbug/95915.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zerolurker/" target="_blank">lurker</a> 2007-01-25 14:04 <a href="http://www.blogjava.net/zerolurker/articles/95915.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java对象池化应用</title><link>http://www.blogjava.net/zerolurker/articles/95914.html</link><dc:creator>lurker</dc:creator><author>lurker</author><pubDate>Thu, 25 Jan 2007 06:02:00 GMT</pubDate><guid>http://www.blogjava.net/zerolurker/articles/95914.html</guid><wfw:comment>http://www.blogjava.net/zerolurker/comments/95914.html</wfw:comment><comments>http://www.blogjava.net/zerolurker/articles/95914.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zerolurker/comments/commentRss/95914.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zerolurker/services/trackbacks/95914.html</trackback:ping><description><![CDATA[
		<div>
				<h2>
						<font color="#0000ff">使用Jakarta Commons Pool处理对象池化</font>
				</h2>内容： <br /><br />对象池化技术 <br />Jakarta Commons Pool组件 <br />下载和安装 <br />PoolableObjectFactory、ObjectPool和ObjectPoolFactory <br />创立PoolableObjectFactory <br />使用ObjectPool <br />利用ObjectPoolFactory <br />借助BasePoolableObjectFactory <br />各式各样的ObjectPool <br />StackObjectPool <br />SoftReferenceObjectPool <br />GenericObjectPool <br />GenericObjectPool.Config <br />带键值的对象池 <br />当出借少于归还 <br />线程安全问题 <br />什么时候不要池化 <br />结束语 <br />参考资料 <br />关于作者 <br />对于本文的评价 <br /><br /><br />恰当地使用对象池化技术，可以有效地减少对象生成和初始化时的消耗，提高系统的运行效率。Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架，以及若干种各具特色的对象池实现，可以有效地减少处理对象池化时的工作量，为其它重要的工作留下更多的精力和时间。<br />创建新的对象并初始化的操作，可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作（例如，从一台位于20,000千米以外的主机上读出一些数据）的时候，尤其是这样。在需要大量生成这样的对象的时候，就可能会对性能造成一些不可忽略的影响。要缓解这个问题，除了选用更好的硬件和更棒的虚拟机以外，适当地采用一些能够减少对象创建次数的编码技巧，也是一种有效的对策。对象池化技术（Object Pooling）就是这方面的著名技巧，而Jakarta Commons Pool组件则是处理对象池化的得力外援。 <br /><br />对象池化技术<br />对象池化的基本思路是：将用过的对象保存起来，等下一次需要这种对象的时候，再拿出来重复使用，从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象，被称为“对象池”（Object Pool，或简称Pool）。 <br /><br />对于没有状态的对象（例如String），在重复使用之前，无需进行任何处理；对于有状态的对象（例如StringBuffer），在重复使用之前，就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制，恢复某个对象的状态的操作不可能实现了的话，就得把这个对象抛弃，改用新创建的实例了。 <br /><br />并非所有对象都适合拿来池化??因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化，反而可能会出现“维护对象池的开销”大于“生成新对象的开销”，从而使性能降低的情况。但是对于生成时开销可观的对象，池化技术就是提高性能的有效策略了。 <br /><br />说明：英语中的Pool除了“池”之外，还有“供多方共享的资源”意思。作者十分怀疑第二种才是“Object Pool”中的Pool的实际含义，但是“对象池”的说法已经广为流传，而一时又没有足以替代的贴切译法，因此这里仍然沿用这种译名。 <br />Jakarta Commons Pool组件 <br /><br /><br />Jakarta Commons Pool是一个用于在Java程序中实现对象池化的组件。它的基本情况是： <br /><br />主要作者：Morgan Delagrange、Geir Magnusson、Craig McClanahan、Rodney Waldhoff、David Weinrich和Dirk Verbeeck <br /><br /><br />最新版本：1.1 <br /><br /><br />所含包数：2个（org.apache.commons.pool和org.apache.commons.pool.impl） <br /><br /><br />所含类数：21个（其中有4个抽象类和6个接口） <br /><br /><br />适用平台：Java 2, Standard Edition. <br /><br /><br />单纯地使用Pool组件不需要太多的Java 2的知识和经验，对语法和基本概念（对象、异常、类、接口、实例、继承和实现等）有一般了解即可。 <br /><br /><br />下载和安装 <br />为了顺利的按照本文中提到的方法使用Pool组件，除去Java 2 SDK外，还需要先准备下列一些东西： <br /><br />Jakarta Commons Pool<br /><br />所需版本：1.0.1+ <br />下载地址：<a href="http://jakarta.apache.org/commons/pool"><font color="#666666"> http://jakarta.apache.org/commons/pool</font></a><br />作用：处理对象池化 <br />Jakarta Commons Collections <br /><br />所需版本：2.1+ <br />下载地址：<a href="http://jakarta.apache.org/commons/collections"><font color="#666666"> http://jakarta.apache.org/commons/collections</font></a><br />作用：支持Jakarta Commons Pool的运行 <br />以上两种软件均有已编译包和源代码包两种形式可供选择。一般情况下，使用已编译包即可。不过建议同时也下载源代码包，作为参考资料使用。 <br /><br />如果打算使用源代码包自行编译，那么还需要准备以下一些东西： <br /><br />Ant <br /><br />所需版本：1.5.3+ <br />下载地址：<a href="http://ant.apache.org/"><font color="#666666"> http://ant.apache.org</font></a><br />作用：运行编译用脚本 <br />JUnit <br /><br />所需版本：3.8.1+ <br />下载地址：<a href="http://www.junit.org/"><font color="#666666"> http://www.junit.org</font></a><br />作用：编译和运行单元测试 <br />具体的编译方法，可以参看有关的Ant文档。 <br /><br />将解压或编译后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH，就可以开始使用Pool组件了。 <br /><br />PoolableObjectFactory、ObjectPool和ObjectPoolFactory <br />在Pool组件中，对象池化的工作被划分给了三类对象： <br /><br />PoolableObjectFactory用于管理被池化的对象的产生、激活、挂起、校验和销毁；<br /><br /><br />ObjectPool用于管理要被池化的对象的借出和归还，并通知PoolableObjectFactory完成相应的工作； <br /><br /><br />ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。 <br /><br /><br />相应地，使用Pool组件的过程，也大体可以划分成“创立PoolableObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三种动作。 <br /><br />创立PoolableObjectFactory <br />Pool组件利用PoolableObjectFactory来照看被池化的对象。ObjectPool的实例在需要处理被池化的对象的产生、激活、挂起、校验和销毁工作时，就会调用跟它关联在一起的PoolableObjectFactory实例的相应方法来操作。 <br /><br />PoolableObjectFactory是在org.apache.commons.pool包中定义的一个接口。实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现，需要根据情况自行创立。 <br /><br />创立PoolableObjectFactory的大体步骤是： <br /><br />创建一个实现了PoolableObjectFactory接口的类。<br /><br /><br />import org.apache.commons.pool.PoolableObjectFactory;<br /><br />public class PoolableObjectFactorySample<br />implements PoolableObjectFactory {<br />private static int counter = 0;<br />}<br /><br /><br /><br /><br />为这个类添加一个Object makeObject()方法。这个方法用于在必要时产生新的对象。 <br /><br /><br />public Object makeObject() throws Exception {<br />Object obj = String.valueOf(counter++);<br />System.err.println("Making Object " + obj);<br />return obj;<br />}<br /><br /><br /><br /><br /><br />为这个类添加一个void activateObject(Object obj)方法。这个方法用于将对象“激活”??设置为适合开始使用的状态。 <br /><br />public void activateObject(Object obj) throws Exception {<br />System.err.println("Activating Object " + obj);<br />}<br /><br /><br /><br /><br /><br />为这个类添加一个void passivateObject(Object obj)方法。这个方法用于将对象“挂起”??设置为适合开始休眠的状态。 <br /><br />public void passivateObject(Object obj) throws Exception {<br />System.err.println("Passivating Object " + obj);<br />}<br /><br /><br /><br /><br /><br />为这个类添加一个boolean validateObject(Object obj)方法。这个方法用于校验一个具体的对象是否仍然有效，已失效的对象会被自动交给destroyObject方法销毁 <br /><br />public boolean validateObject(Object obj) {<br />boolean result = (Math.random() &gt; 0.5);<br />System.err.println("Validating Object "<br />+ obj + " : " + result);<br />return result;<br />}<br /><br /><br /><br /><br />为这个类添加一个void destroyObject(Object obj)方法。这个方法用于销毁被validateObject判定为已失效的对象。 <br /><br />public void destroyObject(Object obj) throws Exception {<br />System.err.println("Destroying Object " + obj);<br />}<br /><br /><br /><br /><br />最后完成的PoolableObjectFactory类似这个样子： <br /><br /><br />PoolableObjectFactorySample.java<br /><br /><br /><br />import org.apache.commons.pool.PoolableObjectFactory;<br /><br />public class PoolableObjectFactorySample<br />implements PoolableObjectFactory {<br />private static int counter = 0;<br /><br />public Object makeObject() throws Exception {<br />Object obj = String.valueOf(counter++);<br />System.err.println("Making Object " + obj);<br />return obj;<br />}<br /><br />public void activateObject(Object obj) throws Exception {<br />System.err.println("Activating Object " + obj);<br />}<br /><br />public void passivateObject(Object obj) throws Exception {<br />System.err.println("Passivating Object " + obj);<br />}<br /><br />public boolean validateObject(Object obj) {<br />/* 以1/2的概率将对象判定为失效 */<br />boolean result = (Math.random() &gt; 0.5);<br />System.err.println("Validating Object "<br />+ obj + " : " + result);<br />return result;<br />}<br /><br />public void destroyObject(Object obj) throws Exception {<br />System.err.println("Destroying Object " + obj);<br />}<br />}<br /><br /><br /><br /><br />使用ObjectPool <br />有了合适的PoolableObjectFactory之后，便可以开始请出ObjectPool来与之同台演出了。 <br /><br />ObjectPool是在org.apache.commons.pool包中定义的一个接口，实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现，可以直接利用。如果都不合用，也可以根据情况自行创建。具体的创建方法，可以参看Pool组件的文档和源码。 <br /><br />ObjectPool的使用方法类似这样： <br /><br />生成一个要用的PoolableObjectFactory类的实例。 <br /><br />PoolableObjectFactory factory = new PoolableObjectFactorySample();<br /><br /><br /><br /><br />利用这个PoolableObjectFactory实例为参数，生成一个实现了ObjectPool接口的类（例如StackObjectPool）的实例，作为对象池。 <br /><br />ObjectPool pool = new StackObjectPool(factory);<br /><br /><br /><br /><br />需要从对象池中取出对象时，调用该对象池的Object borrowObject()方法。 <br /><br />Object obj = null;<br />obj = pool.borrowObject();<br /><br /><br /><br /><br />需要将对象放回对象池中时，调用该对象池的void returnObject(Object obj)方法。 <br /><br />pool.returnObject(obj);<br /><br /><br /><br /><br />当不再需要使用一个对象池时，调用该对象池的void close()方法，释放它所占据的资源。 <br /><br />pool.close();<br /><br /><br /><br /><br />这些操作都可能会抛出异常，需要另外处理。 <br /><br />比较完整的使用ObjectPool的全过程，可以参考这段代码： <br /><br /><br />ObjectPoolSample.java <br /><br /><br /><br />import org.apache.commons.pool.ObjectPool;<br />import org.apache.commons.pool.PoolableObjectFactory;<br />import org.apache.commons.pool.impl.StackObjectPool;<br /><br />public class ObjectPoolSample {<br /><br />public static void main(String[] args) {<br />Object obj = null;<br />PoolableObjectFactory factory <br />= new PoolableObjectFactorySample();<br />ObjectPool pool = new StackObjectPool(factory);<br />try {<br />for(long i = 0; i &lt; 100 ; i++) {<br />System.out.println("== " + i + " ==");<br />obj = pool.borrowObject();<br />System.out.println(obj);<br />pool.returnObject(obj);<br />}<br />obj = null;//明确地设为null，作为对象已归还的标志<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />finally {<br />try{<br />if (obj != null) {//避免将一个对象归还两次<br />pool.returnObject(obj);<br />}<br />pool.close();<br />}<br />catch (Exception e){<br />e.printStackTrace();<br />}<br />}<br />}<br />}<br /><br /><br /><br /><br />另外，ObjectPool接口还定义了几个可以由具体的实现决定要不要支持的操作，包括： <br /><br />void clear() <br /><br />清除所有当前在此对象池中休眠的对象。<br /><br />int getNumActive() <br /><br />返回已经从此对象池中借出的对象的总数。 <br /><br />int getNumIdle() <br /><br />返回当前在此对象池中休眠的对象的数目。 <br /><br />void setFactory(PoolableObjectFactory factory) <br /><br />将当前对象池与参数中给定的PoolableObjectFactory相关联。如果在当前状态下，无法完成这一操作，会有一个IllegalStateException异常抛出。 <br /><br />如果所用的ObjectPool实现不支持这些操作，那么调用这些方法的时候，会抛出一个UnsupportedOperationException异常。 <br />利用ObjectPoolFactory <br />有时候，要在多处生成类型和设置都相同的ObjectPool。如果在每个地方都重写一次调用相应构造方法的代码，不但比较麻烦，而且日后修改起来，也有所不便。这种时候，正是使用ObjectPoolFactory的时机。 <br /><br />ObjectPoolFactory是一个在org.apache.commons.pool中定义的接口，它定义了一个称为ObjectPool createPool()方法，可以用于大量生产类型和设置都相同的ObjectPool。 <br /><br />Pool组件中，对每一个ObjectPool实现，都有一个对应的ObjectPoolFactory实现。它们相互之间，有一一对应的参数相同的构造方法。使用的时候，只要先用想要的参数和想用的ObjectPoolFactory实例，构造出一个ObjectPoolFactory对象，然后在需要生成ObjectPool的地方，调用这个对象的createPool()方法就可以了。日后无论想要调整所用ObjectPool的参数还是类型，只需要修改这一处，就可以大功告成了。 <br /><br />将《使用ObjectPool》一节中的例子，改为使用ObjectPoolFactory来生成所用的ObjectPool对象之后，基本就是这种形式： <br /><br /><br />ObjectPoolFactorySample.java<br /><br /><br />import org.apache.commons.pool.ObjectPool;<br />import org.apache.commons.pool.ObjectPoolFactory;<br />import org.apache.commons.pool.PoolableObjectFactory;<br />import org.apache.commons.pool.impl.StackObjectPoolFactory;<br /><br />public class ObjectPoolFactorySample {<br /><br />public static void main(String[] args) {<br />Object obj = null;<br />PoolableObjectFactory factory<br />= new PoolableObjectFactorySample();<br />ObjectPoolFactory poolFactory<br />= new StackObjectPoolFactory(factory);<br />ObjectPool pool = poolFactory.createPool();<br />try {<br />for(long i = 0; i &lt; 100 ; i++) {<br />System.out.println("== " + i + " ==");<br />obj = pool.borrowObject();<br />System.out.println(obj);<br />pool.returnObject(obj);<br />}<br />obj = null;<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />finally {<br />try{<br />if (obj != null) {<br />pool.returnObject(obj);<br />}<br />pool.close();<br />}<br />catch (Exception e){<br />e.printStackTrace();<br />}<br />}<br />}<br />}<br /><br /><br /><br /><br />借助BasePoolableObjectFactory <br />PoolableObjectFactory定义了许多方法，可以适应多种不同的情况。但是，在并没有什么特殊需要的时候，直接实现PoolableObjectFactory接口，就要编写若干的不进行任何操作，或是始终返回true的方法来让编译通过，比较繁琐。这种时候就可以借助BasePoolableObjectFactory的威力，来简化编码的工作。 <br /><br />BasePoolableObjectFactory是org.apache.commons.pool包中的一个抽象类。它实现了PoolableObjectFactory接口，并且为除了makeObject之外的方法提供了一个基本的实现??activateObject、passivateObject和destroyObject不进行任何操作，而validateObject始终返回true。通过继承这个类，而不是直接实现PoolableObjectFactory接口，就可以免去编写一些只起到让编译通过的作用的代码的麻烦了。 <br /><br />这个例子展示了一个从BasePoolableObjectFactory扩展而来的PoolableObjectFactory： <br /><br /><br />BasePoolableObjectFactorySample.java <br /><br /><br /><br />import org.apache.commons.pool.BasePoolableObjectFactory;<br /><br />public class BasePoolableObjectFactorySample <br />extends BasePoolableObjectFactory {<br /><br />private int counter = 0;<br /><br />public Object makeObject() throws Exception {<br />return String.valueOf(counter++);<br />}<br />}<br /><br /><br /><br /><br />各式各样的ObjectPool <br />可口可乐公司的软饮料有可口可乐、雪碧和芬达等品种，百事可乐公司的软饮料有百事可乐、七喜和美年达等类型，而Pool组件提供的ObjectPool实现则有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等种类。 <br /><br />不同类型的软饮料各有各自的特点，分别适应不同消费者的口味；而不同类型的ObjectPool也各有各自的特色，分别适应不同的情况。 <br /><br />StackObjectPool <br />StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是： <br /><br />可以为对象池指定一个初始的参考大小（当空间不够时会自动增长）。 <br />在对象池已空的时候，调用它的borrowObject方法，会自动返回新创建的实例。 <br />可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后，再向池里送回的对象会被自动送去回收。 <br />StackObjectPool的构造方法共有六个，其中： <br /><br />最简单的一个是StackObjectPool()，一切采用默认的设置，也不指明要用的PoolableObjectFactory实例。 <br />最复杂的一个则是StackObjectPool(PoolableObjectFactory factory, int max, int init)。其中：<br /><br />参数factory指明要与之配合使用的PoolableObjectFactory实例； <br />参数max设定可保存对象数目的上限； <br />参数init则指明初始的参考大小。 <br />剩余的四个构造方法则是最复杂的构造方法在某方面的简化版本，可以根据需要选用。它们是： <br /><br />StackObjectPool(int max) <br />StackObjectPool(int max, int init) <br />StackObjectPool(PoolableObjectFactory factory) <br />StackObjectPool(PoolableObjectFactory factory, int max) <br />用不带factory参数的构造方法构造的StackObjectPool实例，必须要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。 <br /><br />这种对象池可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。 <br /><br />SoftReferenceObjectPool <br />SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身，而是保存它们的“软引用”（Soft Reference）。这种对象池的特色是： <br /><br />可以保存任意多个对象，不会有容量已满的情况发生。 <br />在对象池已空的时候，调用它的borrowObject方法，会自动返回新创建的实例。 <br />可以在初始化同时，在池内预先创建一定量的对象。 <br />当内存不足的时候，池中的对象可以被Java虚拟机回收。 <br />SoftReferenceObjectPool的构造方法共有三个，其中： <br /><br />最简单的是SoftReferenceObjectPool()，不预先在池内创建对象，也不指明要用的PoolableObjectFactory实例。 <br />最复杂的一个则是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中： <br /><br />参数factory指明要与之配合使用的PoolableObjectFactory实例 <br />参数initSize则指明初始化时在池中创建多少个对象。 <br />剩下的一个构造方法，则是最复杂的构造方法在某方面的简化版本，适合在大多数情况下使用。它是：<br /><br />SoftReferenceObjectPool(PoolableObjectFactory factory) <br />用不带factory参数的构造方法构造的SoftReferenceObjectPool实例，也要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。 <br /><br />这种对象池也可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。 <br /><br />GenericObjectPool <br />GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是： <br /><br />可以设定最多能从池中借出多少个对象。 <br />可以设定池中最多能保存多少个对象。 <br />可以设定在池中已无对象可借的情况下，调用它的borrowObject方法时的行为，是等待、创建新的实例还是抛出异常。 <br />可以分别设定对象借出和还回时，是否进行有效性检查。 <br />可以设定是否使用一个单独的线程，对池内对象进行后台清理。 <br />GenericObjectPool的构造方法共有七个，其中： <br /><br />最简单的一个是GenericObjectPool(PoolableObjectFactory factory)。仅仅指明要用的PoolableObjectFactory实例，其它参数则采用默认值。 <br />最复杂的一个是GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle)。其中： <br /><br />参数factory指明要与之配合使用的PoolableObjectFactory实例。 <br />参数maxActive指明能从池中借出的对象的最大数目。如果这个值不是正数，表示没有限制。 <br />参数whenExhaustedAction指定在池中借出对象的数目已达极限的情况下，调用它的borrowObject方法时的行为。可以选用的值有： <br />GenericObjectPool.WHEN_EXHAUSTED_BLOCK，表示等待； <br />GenericObjectPool.WHEN_EXHAUSTED_GROW，表示创建新的实例（不过这就使maxActive参数失去了意义）； <br />GenericObjectPool.WHEN_EXHAUSTED_FAIL，表示抛出一个java.util.NoSuchElementException异常。 <br />参数maxWait指明若在对象池空时调用borrowObject方法的行为被设定成等待，最多等待多少毫秒。如果等待时间超过了这个数值，则会抛出一个java.util.NoSuchElementException异常。如果这个值不是正数，表示无限期等待。 <br />参数testOnBorrow设定在借出对象时是否进行有效性检查。 <br />参数testOnBorrow设定在还回对象时是否进行有效性检查。 <br />参数timeBetweenEvictionRunsMillis，设定间隔每过多少毫秒进行一次后台对象清理的行动。如果这个值不是正数，则实际上不会进行后台对象清理。 <br />参数numTestsPerEvictionRun，设定在进行后台对象清理时，每次检查几个对象。如果这个值不是正数，则每次检查的对象数是检查时池内对象的总数乘以这个值的负倒数再向上取整的结果??也就是说，如果这个值是-2（-3、-4、-5……）的话，那么每次大约检查当时池内对象总数的1/2（1/3、1/4、1/5……）左右。 <br />参数minEvictableIdleTimeMillis，设定在进行后台对象清理时，视休眠时间超过了多少毫秒的对象为过期。过期的对象将被回收。如果这个值不是正数，那么对休眠时间没有特别的约束。 <br />参数testWhileIdle，则设定在进行后台对象清理时，是否还对没有过期的池内对象进行有效性检查。不能通过有效性检查的对象也将被回收。 <br />另一个比较特别的构造方法是GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 。其中： <br /><br />参数factory指明要与之配合使用的PoolableObjectFactory实例； <br />参数config则指明一个包括了各个参数的预设值的对象（详见《GenericObjectPool.Config》一节）。 <br />剩下的五个构造函数则是最复杂的构造方法在某方面的简化版本，可以根据情况选用。它们是： <br /><br />GenericObjectPool(PoolableObjectFactory factory, int maxActive) <br />GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait) <br />GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, boolean testOnBorrow, boolean testOnReturn) <br />GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle) <br />GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn) <br />这种对象池不可以在没有Jakarta Commmons Collections组件支持的情况下运行。 <br /><br />GenericObjectPool.Config <br />调用一个有很多的参数的方法的时候，很可能将参数的位置和个数搞错，导致编译或运行时的错误；阅读包含了有很多参数的方法调用的代码的时候，也很可能因为没有搞对参数的位置和个数，产生错误的理解。因此，人们往往避免给一个方法安排太多的参数的做法（所谓的“Long Parameter List”）。不过，有些方法又确实需要许多参数才能完成工作。于是，就有人想到了一种将大批的参数封装到一个对象（称为参数对象，Parameter Object）里，然后将这个对象作为单一的参数传递的两全其美的对策。 <br /><br />因为生成GenericKeyedObjectPool时可供设置的特性非常之多，所以它的构造方法里也就难免会需要不少的参数。GenericKeyedObjectPool除了提供了几个超长的构造方法之外，同时也定义了一个使用参数对象的构造方法。所用参数对象的类型是GenericKeyedObjectPool.Config。 <br /><br />GenericKeyedObjectPool.Config定义了许多的public字段，每个对应一种可以为GenericKeyedObjectPool设置的特性，包括： <br /><br />int maxActive <br />int maxIdle <br />long maxWait <br />long minEvictableIdleTimeMillis <br />int numTestsPerEvictionRun <br />boolean testOnBorrow <br />boolean testOnReturn <br />boolean testWhileIdle <br />long timeBetweenEvictionRunsMillis <br />byte whenExhaustedAction <br />这些字段的作用，与在GenericKeyedObjectPool最复杂的构造方法中与它们同名的参数完全相同。 <br /><br />使用的时候，先生成一个GenericKeyedObjectPool.Config对象，然后将个字段设置为想要的值，最后用这个对象作为唯一的参数调用GenericKeyedObjectPool的构造方法即可。 <br /><br />注意：使用有许多public字段、却没有任何方法的类，也是一个人们往往加以避免的行为（所谓的“Data Class”）。不过这次GenericKeyedObjectPool特立独行了一回。 <br /><br />带键值的对象池 <br />有时候，单用对池内所有对象一视同仁的对象池，并不能解决的问题。例如，对于一组某些参数设置不同的同类对象??比如一堆指向不同地址的java.net.URL对象或者一批代表不同语句的java.sql.PreparedStatement对象，用这样的方法池化，就有可能取出不合用的对象的麻烦。 <br /><br />可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是，如果使用普通的ObjectPool来实施这个计策的话，因为普通的PoolableObjectFactory只能生产出大批设置完全一致的对象，就需要为每一组参数相同的对象编写一个单独的PoolableObjectFactory，工作量相当可观。这种时候就适合调遣Pool组件中提供的一种“带键值的对象池”来展开工作了。 <br /><br />Pool组件采用实现了KeyedObjectPool接口的类，来充当带键值的对象池。相应的，这种对象池需要配合实现了KeyedPoolableObjectFactory接口的类和实现了KeyedObjectPoolFactory接口的类来使用（这三个接口都在org.apache.commons.pool包中定义）： <br /><br />KeyedPoolableObjectFactory和PoolableObjectFactory形式如出一辙，只是每个方法都增加了一个Object key参数而已： <br /><br />makeObject的参数变为(Object key) <br />activateObject的参数变为(Object key, Object obj) <br />passivateObject的参数变为(Object key, Object obj) <br />validateObject的参数变为Object key, Object obj) <br />destroyObject的参数变为(Object key, Object obj) <br />另外Pool组件也提供了BaseKeyedPoolableObjectFactory，用于充当和BasePoolableObjectFactory差不多的角色。<br /><br />KeyedObjectPool和ObjectPool的形式大同小异，只是某些方法的参数类型发生了变化，某些方法分成了两种略有不同的版本： <br /><br />用Object borrowObject(Object key)和void returnObject(Object key, Object obj)来负责对象出借和归还的动作。 <br />用void close()来关闭不再需要的对象池。 <br />用void clear(Object key)和void clear()来清空池中的对象，前者针对与特定键值相关联的实例，后者针对整个对象池。 <br />用int getNumActive(Object key)和int getNumActive()来查询已借出的对象数，前者针对与特定键值相关联的实例，后者针对整个对象池。 <br />用int getNumIdle(Object key)和int getNumIdle()来查询正在休眠的对象数，前者针对与特定键值相关联的实例，后者针对整个对象池。 <br />用void setFactory(KeyedPoolableObjectFactory factory)来设置要用的KeyedPoolableObjectFactory实例。 <br />void clear、int getNumActive、int getNumIdle和void setFactory的各种版本都仍然是可以由具体实现自行决定是否要支持的方法。如果所用的KeyedObjectPool实现不支持这些操作，那么调用这些方法的时候，会抛出一个UnsupportedOperationException异常。 <br /><br />KeyedObjectPoolFactory和ObjectPoolFactory的形式完全相同，只是所代表的对象不同而已。 <br />这一类对象池的基本使用方法接近于这样： <br /><br /><br />KeyedObjectPoolSample.java <br /><br /><br /><br />import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;<br />import org.apache.commons.pool.KeyedObjectPool;<br />import org.apache.commons.pool.KeyedObjectPoolFactory;<br />import org.apache.commons.pool.KeyedPoolableObjectFactory;<br />import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;<br /><br />class KeyedPoolableObjectFactorySample<br />extends BaseKeyedPoolableObjectFactory {<br /><br />public Object makeObject(Object key) throws Exception {<br />return new String("[" + key.hashCode() + "]");<br />}<br /><br />}<br /><br />public class KeyedObjectPoolSample {<br />public static void main(String[] args) {<br />Object obj = null;<br />KeyedPoolableObjectFactory factory<br />= new KeyedPoolableObjectFactorySample();<br />KeyedObjectPoolFactory poolFactory <br />= new StackKeyedObjectPoolFactory(factory);<br />KeyedObjectPool pool = poolFactory.createPool();<br />String key = null;<br />try {<br />for (long i = 0; i &lt; 100 ; i++) {<br />key = "" + (int) (Math.random() * 10);<br />System.out.println("== " + i + " ==");<br />System.out.println("Key:" + key);<br />obj = pool.borrowObject(key);<br />System.out.println("Object:" + obj);<br />pool.returnObject(key, obj);<br />obj = null;<br />}<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />finally {<br />try{<br />if (obj != null) {<br />pool.returnObject(key, obj);<br />}<br />pool.close();<br />}<br />catch (Exception e){<br />e.printStackTrace();<br />}<br />}<br />}<br />}<br /><br /><br /><br /><br /><br />Pool组件自带的KeyedObjectPool的实现有StackKeyedObjectPool和GenericKeyedObjectPool两种。它们的使用方法分别与它们各自的近亲KeyedObjectPool和KeyedObjectPool基本一致，只是原来使用GenericObjectPool.Config的地方要使用GenericKeyedObjectPool.Config代替。 <br /><br />当出借少于归还 <br />Java并未提供一种机制来保证两个方法被调用的次数之间呈现一种特定的关系（相等，相差一个常数，或是其它任何关系）。因此，完全可以做到建立一个ObjectPool对象，然后调用一次borrowObject方法，借出一个对象，之后重复两次returnObject方法调用，进行两次归还。而调用一个从不曾借出对象的ObjectPool的returnObject方法也并不是一个不可完成的任务。 <br /><br />尽管这些使用方法并不合乎returnObject的字面意思，但是Pool组件中的各个ObjectPool/KeyedObjectPool实现都不在乎这一点。它们的returnObject方法都只是单纯地召唤与当前对象池关联的PoolableObjectFactory实例，看这对象能否经受得起validateObject的考验而已。考验的结果决定了这个对象是应该拿去作passivateObject处理，而后留待重用；还是应该拿去作destroyObject处理，以免占用资源。也就是说，当出借少于归还的时候，并不会额外发生什么特别的事情（当然，有可能因为该对象池处于不接受归还对象的请求的状态而抛出异常，不过这是常规现象）。 <br /><br />在实际使用中，可以利用这一特性来向对象池内加入通过其它方法生成的对象。 <br /><br />线程安全问题<br />有时候可能要在多线程环境下使用Pool组件，这时候就会遇到和Pool组件的线程安全程度有关的问题。 <br /><br />因为ObjectPool和KeyedObjectPool都是在org.apache.commons.pool中定义的接口，而在接口中无法使用“synchronized”来修饰方法，所以，一个ObjectPool/KeyedObjectPool下的各个方法是否是同步方法，完全要看具体的实现。而且，单纯地使用了同步方法，也并不能使对象就此在多线程环境里高枕无忧。 <br /><br />就Pool组件中自带的几个ObjectPool/KeyedObjectPool的实现而言，它们都在一定程度上考虑了在多线程环境中使用的情况。不过还不能说它们是完全“线程安全”的。 <br /><br />例如，这段代码有些时候就会有一些奇怪的表现，最后输出的结果比预期的要大： <br /><br /><br />UnsafeMultiThreadPoolingSample.java<br /><br /><br /><br />import org.apache.commons.pool.ObjectPool;<br />import org.apache.commons.pool.impl.StackObjectPool;<br /><br />class UnsafePicker extends Thread {<br />private ObjectPool pool;<br />public UnsafePicker(ObjectPool op) {<br />pool = op;<br />}<br />public void run() {<br />Object obj = null;<br />try {<br />/* 似乎…… */<br />if ( pool.getNumActive() &lt; 5 ) {<br />sleep((long) (Math.random() * 10));<br />obj = pool.borrowObject();<br />}<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />}<br />}<br /><br />public class UnsafeMultiThreadPoolingSample {<br /><br />public static void main(String[] args) {<br />ObjectPool pool = new StackObjectPool<br />(new BasePoolableObjectFactorySample());<br />Thread ts[] = new Thread[20];<br />for (int j = 0; j &lt; ts.length; j++) {<br />ts[j] = new UnsafePicker(pool);<br />ts[j].start();<br />}<br />try {<br />Thread.sleep(1000);<br />/* 然而…… */<br />System.out.println("NumActive:" + pool.getNumActive());<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />}<br />}<br /><br /><br /><br /><br /><br /><br />要避免这种情况，就要进一步采取一些措施才行： <br /><br /><br />SafeMultiThreadPoolingSample.java <br /><br /><br /><br />import org.apache.commons.pool.ObjectPool;<br />import org.apache.commons.pool.impl.StackObjectPool;<br /><br />class SafePicker extends Thread {<br />private ObjectPool pool;<br />public SafePicker(ObjectPool op) {<br />pool = op;<br />}<br />public void run() {<br />Object obj = null;<br />try {<br />/* 略加处理 */<br />synchronized (pool) {<br />if ( pool.getNumActive() &lt; 5 ) {<br />sleep((long) (Math.random() * 10));<br />obj = pool.borrowObject();<br />}<br />}<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />}<br />}<br /><br />public class SafeMultiThreadPoolingSample {<br /><br />public static void main(String[] args) {<br />ObjectPool pool = new StackObjectPool<br />(new BasePoolableObjectFactorySample());<br />Thread ts[] = new Thread[20];<br />for (int j = 0; j &lt; ts.length; j++) {<br />ts[j] = new SafePicker(pool);<br />ts[j].start();<br />}<br />try {<br />Thread.sleep(1000);<br />System.out.println("NumActive:" + pool.getNumActive());<br />}<br />catch (Exception e) {<br />e.printStackTrace();<br />}<br />}<br />}<br /><br /><br /><br /><br />基本上，可以说Pool组件是线程相容的。但是要在多线程环境中使用，还需要作一些特别的处理。<br /><br />什么时候不要池化 <br />采用对象池化的本意，是要通过减少对象生成的次数，减少花在对象初始化上面的开销，从而提高整体的性能。然而池化处理本身也要付出代价，因此，并非任何情况下都适合采用对象池化。 <br /><br />Dr. Cliff Click在JavaOne 2003上发表的《Performance Myths Exposed》中，给出了一组其它条件都相同时，使用与不使用对象池化技术的实际性能的比较结果。他的实测结果表明： <br /><br />对于类似Point这样的轻量级对象，进行池化处理后，性能反而下降，因此不宜池化； <br /><br />对于类似Hashtable这样的中量级对象，进行池化处理后，性能基本不变，一般不必池化（池化会使代码变复杂，增大维护的难度）； <br />对于类似JPanel这样的重量级对象，进行池化处理后，性能有所上升，可以考虑池化。 <br />根据使用方法的不同，实际的情况可能与这一测量结果略有出入。在配置较高的机器和技术较强的虚拟机上，不宜池化的对象的范围可能会更大。不过，对于像网络和数据库连接这类重量级的对象来说，目前还是有池化的必要。 <br /><br />基本上，只在重复生成某种对象的操作成为影响性能的关键因素的时候，才适合进行对象池化。如果进行池化所能带来的性能提高并不重要的话，还是不采用对象池化技术，以保持代码的简明，而使用更好的硬件和更棒的虚拟机来提高性能为佳。 <br /><br />结束语 <br />恰当地使用对象池化，可以有效地降低频繁生成某些对象所造成的开销，从而提高整体的性能。而借助Jakarta Commons Pool组件，可以有效地减少花在处理对象池化上的工作量，进而，向其它重要的工作里，投入更多的时间和精力。 <br /><br />参考资料 <br /><br />很多疑难问题的答案都可以通过查阅Pool组件的Javadoc文档和源代码的方法解决。<br /><br /><br />从 Pool组件的官方站点 上，还可以进一步得到许多有用的信息。 <br /><br /><br />DBCP 是一个基于Pool组件的Java数据库连接池管理组件，同时也可以作为Pool组件的用法示例使用。 <br /><br /><br />蔡学镛在 《Java夜未眠（Sleepless Java）》 中的 《测不准原理》一文里，介绍了Java中的包括“软引用”（Soft Reference）在内的各种不同的引用的特点和用处。 <br /><br /><br />Martin Fowler在 《Refactoring -- Improving the Design of Existing Code》（中译本名为《重构??改善既有代码的设计》，由侯捷、熊节合译）一书的第三章《代码的坏味道（Bad Smells in Code）》中讨论了被称为“Long Parameter List”和“Data Class”的“坏味道”。并指明了一些可以用于对付这些问题的重构手法。 <br /><br /><br />Brian Goetz在IBM developerWorks上发表的《Java 理论与实践：描绘线程安全性》一文中，说明了为什么单纯地使用同步方法还不能让对象就此在多线程环境里高枕无忧的原因。 <br /><br /><br />Dr. Cliff Click发表在JavaOne 2003上的《Performance Myths Exposed》（Session #1522），给出了一组包括“对象池化”在内的、对“能提高Java程序性能”的做法的实际效果的测试数据，和一些恰当使用这些做法的建议。 </div>
<img src ="http://www.blogjava.net/zerolurker/aggbug/95914.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zerolurker/" target="_blank">lurker</a> 2007-01-25 14:02 <a href="http://www.blogjava.net/zerolurker/articles/95914.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java类加载的表现形式 </title><link>http://www.blogjava.net/zerolurker/articles/95913.html</link><dc:creator>lurker</dc:creator><author>lurker</author><pubDate>Thu, 25 Jan 2007 06:00:00 GMT</pubDate><guid>http://www.blogjava.net/zerolurker/articles/95913.html</guid><wfw:comment>http://www.blogjava.net/zerolurker/comments/95913.html</wfw:comment><comments>http://www.blogjava.net/zerolurker/articles/95913.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zerolurker/comments/commentRss/95913.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zerolurker/services/trackbacks/95913.html</trackback:ping><description><![CDATA[
		<div>java中的类是动态加载的，我们先看一下我们常用的类加载方式，先有一个感性的认识，才能进一步<br />深入讨论,类加载无非就是下面三种方式。<br />class A{}<br />class B{}<br />class C{}<br />public class Loader{<br />    public static void main(String[] args) throws Exception{<br />       Class aa=A.class; <br />       Class bb=Class.forName("B");<br />       Class cc=ClassLoader.getSystemClassLoader().loadClass("C");<br />    }<br />}<br />我们先看.class字面量方式，很多人可能不知道这种方式，因为这种用法不是一般java语法。<br />通过javap我们可以发现，这种方式的大致等价于定义了一个静态成员变量<br />    static Class class$0;(后面的编号是增长的)<br />你可以试图再定义一个  static Class class$0,应该会收到一个编译错误(重复定义)。</div>
		<div>
		</div>
		<div>Class aa=A.class;<br />就相当于<br />    if(class$0==null){<br /> try{ <br />           Class.forName("A");<br /> }<br /> cacth(ClassNotFoundException e){<br />    throw new NoClassDefFoundError(e);<br /> }<br />    }<br />    Class aa=class$0;</div>
		<div>
		</div>
		<div>可以很清楚的看到，这种类的字面量定义其实不是加载类的方式，而是被编译器处理了，实质<br />上是使用了Class.forName方法，但是使用这种方式有一个很大的好处就是不用处理异常，因为<br />编译器处理的时候如果找不到类会抛出一个NoClassDefFoundError。也许你觉得需要处理<br />ClassNotFoundException这种异常，事实上99%的情况下我们可以把这种异常认为是一个错误。<br />所以大部分情况我们使用这种方式会更简洁。</div>
		<div>
		</div>
		<div>最常用的方式就是Class.forName方式了，这也是一个通用的上层调用。这个方法有两个重载，<br />可能很多人都忽略了第二个方法。<br />public static Class forName(String name) throws ClassNotFoundException<br />public static Class forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException</div>
		<div>
		</div>
		<div>第二个方法后面多了两个参数，第二个参数表示是否初始化，第三个参数为指定的类加载器。<br />在上面的例子中：<br />Class bb=Class.forName("B");等价于<br />Class bb=Class.forName("B",true,Loader.class.getClassLoader());<br />这里要详细说一下这个类的初始化这个参数,如果这个参数为false的话，<br />类中的static成员不会被初始化，static语句块也不会被执行。<br />也就是类虽然被加载了，但是没有被初始化，不过在第一次使用时仍然会初始化。<br />所以我们有时候会看到Class.forName("XXX").newInstance()这样的语句，为什么这里要创建一个<br />不用的实例呢?不过是为了保证类被初始化(兼容以前的系统)。<br />其实第二个方法是比较难用的，需要指定类加载器，如果不指定而且又没有安装安全管理器的化，<br />是无法加载类的，只要看一下具体的实现就明白了。</div>
		<div>
		</div>
		<div>最本质的方式当然是直接使用ClassLoader加载了，所有的类最终都是通过ClassLoader加载的，<br />Class cc=ClassLoader.getSystemClassLoader().loadClass("C");<br />这里通过使用系统类加载器来加载某个类，很直接的方式，但是很遗憾的是通过这种方式加载类，<br />类是没有被初始化的(也就是初始化被延迟到真正使用的时候).不过我们也可以借鉴上面的经验，加载<br />后实例化一个对象Class cc=ClassLoader.getSystemClassLoader().loadClass("C").newInstance()。<br />这里使用了系统类加载器，也是最常用的类加载器，从classpath中寻找要加载的类。<br />java中默认有三种类加载器：引导类加载器，扩展类加载器，系统类加载器。<br />java中的类加载有着规范的层次结构，如果我们要了解类加载的过程，需要明确知道哪个类被谁<br />加载，某个类加载器加载了哪些类等等，就需要深入理解ClassLoader的本质。<br />以上只是类加载的表面的东西，我们还将讨论深层次的东西。</div>
		<div>
		</div>
		<div>
		</div>
		<p>原文：<a href="http://dev.csdn.net/author/treeroot/a481eb323af84caab1149221432e46b9.html"><font color="#666666">http://dev.csdn.net/author/treeroot/a481eb323af84caab1149221432e46b9.html</font></a></p>
<img src ="http://www.blogjava.net/zerolurker/aggbug/95913.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zerolurker/" target="_blank">lurker</a> 2007-01-25 14:00 <a href="http://www.blogjava.net/zerolurker/articles/95913.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>