Java类加载与卸载


http://blog.csdn.net/feiyu8607/article/details/8308314


Java Class卸载与ClassLoader

分类: Java   2047人阅读  评论(0)  收藏  举报
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):

   - 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。
   - 加载该类的ClassLoader已经被GC。
   - 该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法

 
jsp和java类是完全不一样的概念。 
jsp->servlet 在web容器中,你的servlet是单例的,也是无状态的, 线程安全的。也就是只有一个对象,
jsp改变以后,web容器只要把相应的servlet对象更新就好了。 

而java呢? 
可能这个类在你的应用中有n个实例,与这些实例单向,双向关联的又有n个实例。如果你修改了,这些jvm存在的老的实例对象怎么办???? 
java这类静态语言无法实现象asp,php,jsp的效果的。


 weblogic热部署原理

Weblogic允许在wls运行时部署组件的新版本。这个过程被称作热部署。因为java classloader没有任何一种机制来卸下一系列存在的类,也不能用类的新版本来替换老版本,为了在一个运行的虚拟机中更新相关的类,classloader必须被替换掉。当它被替换时,它所装载的所有类以及衍生的子classloader也要被重新装载。这些类的所有实例也必需被重新装载。在wls中,每一个应用组件都有一个层次化的classloaders,它们都是system classloader的子类,这种结构有助于每个应用或应用的一部分能被单独重新加载,而不会影响其它的组件。

 

类加载器的种类:
  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-load-class
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)。

 

三.命名空间及其作用
每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。
 
例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。
例2不同命名空间的类的访问
/*LoaderSample2.java*/

import    java.net. * ; 
import    java.lang.reflect. * ; 
public      class    LoaderSample2 { 
          public      static      void    main(String[] args) { 
                  try    { 
                        String path    =    System.getProperty(  " user.dir " ); 
                        URL[] us    =    {  new    URL(  " file:// "     +    path    +     " /sub/ " )}; 
                        ClassLoader loader    =     new    URLClassLoader(us); 
                        Class c    =    loader.loadClass( " LoaderSample3 " ); 
                        Object o    =    c.newInstance(); 
                        Field f    =    c.getField( " age " ); 
                         int    age    =    f.getInt(o); 
                        System.out.println( " age is    "     +    age); 
                }    catch    (Exception e) { 
                        e.printStackTrace(); 
                } 
        } 
}

/*sub/Loadersample3.java*/

public      class    LoaderSample3 { 
          static    { 
                System.out.println(  " LoaderSample3 loaded " ); 
        } 
          public      int    age    =     30 ; 
}
编译:javac LoaderSample2.java; javac sub/LoaderSample3.java
运行:java LoaderSample2
LoaderSample3 loaded
age is 30
从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。

 

说明:如果LoaderSample3在classpath下能够找到,则由URLClassLoader的parent loader AppClassLoader来加载,如果不在classpath下

则由URLClassLoader自己加载,即LoaderSample3.getClass().getClassLoader() 是URLClassLoader

运行时包(runtime package)
由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.*由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能访问核心类库java.lang中类的包可见的成员。 
 
总结
命名空间并没有完全禁止属于不同空间的类的互相访问,双亲委托模型加强了Java的安全,运行时包增加了对包可见成员的保护。
 
二.    扩展ClassLoader方法
我们目的是从本地文件系统使用我们实现的类装载器装载一个类。 为了创建自己的类装载器我们应该扩展ClassLoader类,这是一个抽象类。我们创建一个FileClassLoader extends ClassLoader。我们需要覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字而得到一个Class对象。
          public    Class findClass(String name) 
        { 
                  byte [] data    =    loadClassData(name); 
                  return    defineClass(name, data,    0 , data.length); 
        }

我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字 
节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。

public      byte [] loadClassData(String name) 
        { 
                FileInputStream fis    =      null ; 
                  byte [] data    =      null ; 
                  try    
                { 
                        fis    =      new    FileInputStream(  new    File(drive    +    name    +    fileType)); 
                        ByteArrayOutputStream baos    =      new    ByteArrayOutputStream(); 
                          int    ch    =     0 ; 
                          while    ((ch    =    fis.read())    !=     - 1 ) 
                        { 
                                baos.write(ch); 
                                
                        } 
                        data    =    baos.toByteArray(); 
                }     catch    (IOException e) 
                { 
                        e.printStackTrace(); 
                } 
                 
                  return    data; 
        }

 

自定义ClassLoader实现java 热替换:http://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/




======================================================================

http://www.cnblogs.com/ITtangtang/p/3978102.html


深入理解Java:类加载机制及反射

 说明:本文乃学习整理参考而来.

一、Java类加载机制

1.概述

       Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。

      虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

2.工作机制

      类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:

     (1) 装载:查找和导入Class文件;

     (2) 链接:把类的二进制数据合并到JRE中;

        (a)校验:检查载入Class文件数据的正确性;

        (b)准备:给类的静态变量分配存储空间;

        (c)解析:将符号引用转成直接引用;

     (3) 初始化:对类的静态变量,静态代码块执行初始化操作

 

 

      Java程序可以动态扩展是由运行期动态加载和动态链接实现的;比如:如果编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现(多态),解析过程有时候还可以在初始化之后执行;比如:动态绑定(多态);

        

      【类初始化】 

      (1) 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

      (2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

      (3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

      (4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

只有上述四种情况会触发初始化,也称为对一个类进行主动引用,除此以外,所有其他方式都不会触发初始化,称为被动引用

代码清单1

 

 

 

 

 

上述代码运行后,只会输出【---SuperClass init】, 而不会输出【SubClass init】,对于静态字段,只有直接定义这个字段的类才会被初始化,因此,通过子类来调用父类的静态字段,只会触发父类的初始化,但是这是要看不同的虚拟机的不同实现。

代码清单2

 

 

 

 

 

此处不会引起SuperClass的初始化,但是却触发了【[Ltest.SuperClass】的初始化,通过arr.toString()可以看出,对于用户代码来说,这不是一个合法的类名称,它是由虚拟机自动生成的,直接继承于Object的子类,创建动作由字节码指令newarray触发,此时数组越界检查也会伴随数组对象的所有调用过程,越界检查并不是封装在数组元素访问的类中,而是封装在数组访问的xaload,xastore字节码指令中.

代码清单3

 

 

 

 

 

对常量ConstClass.value 的引用实际都被转化为NotInitialization类对自身常量池的引用,这两个类被编译成class后不存在任何联系。

 

          【装载】

    在装载阶段,虚拟机需要完成以下3件事情

        (1) 通过一个类的全限定名来获取定义此类的二进制字节流

        (2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

        (3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

    虚拟机规范中并没有准确说明二进制字节流应该从哪里获取以及怎样获取,这里可以通过定义自己的类加载器去控制字节流的获取方式。

        

         【验证】

    虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统奔溃。

 

         【准备】

    准备阶段是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配,需要说明的是:

这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中;这里所说的初始值“通常情况”是数据类型的零值,假如:

public static int value = 123;

value在准备阶段过后的初始值为0而不是123,而把value赋值的putstatic指令将在初始化阶段才会被执行

  

二、类加载器与双亲委派模型

      类加载器

     (1) Bootstrap ClassLoader : 将存放于<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用

     (2) Extension ClassLoader : 将<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs系统变量所指定的路径中的所有类库加载。开发者可以直接使用扩展类加载器。

     (3) Application ClassLoader : 负责加载用户类路径(ClassPath)上所指定的类库,开发者可直接使用。

     

双亲委派模型

 

工作过程:如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

     好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。

 

       java.lang.ClassLoader中几个最重要的方法:

复制代码
//加载指定名称(包括包名)的二进制类型,供用户调用的接口
public Class<?> loadClass(String name);
//加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是,这里的resolve参数不一定真正能达到解析的效果),供继承用
protected synchronized Class<?> loadClass(String name, boolean resolve);
protected Class<?> findClass(String name)
//定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}
复制代码

如下是实现双亲委派模型的主要代码:

 

 

 

 

 

 

 

 

 

 

 

三、反射

      Reflection机制允许程序在正在执行的过程中,利用Reflection APIs取得任何已知名称的类的内部信息,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在执行的过程中,动态生成instances、变更fields内容或唤起methods。

      1、获取构造方法

Class类提供了四个public方法,用于获取某个类的构造方法。

Constructor getConstructor(Class[] params)     

根据构造函数的参数,返回一个具体的具有public属性的构造函数

    Constructor getConstructors()     

返回所有具有public属性的构造函数数组

    Constructor getDeclaredConstructor(Class[] params)     

根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)

    Constructor getDeclaredConstructors()    

返回该类中所有的构造函数数组(不分public和非public属性)

 

 

 

 

 

 

 

 

 

 

 

2、获取类的成员方法

与获取构造方法的方式相同,存在四种获取成员方法的方式。 

Method getMethod(String name, Class[] params)    

根据方法名和参数,返回一个具体的具有public属性的方法

    Method[] getMethods()    

返回所有具有public属性的方法数组

    Method getDeclaredMethod(String name, Class[] params)    

根据方法名和参数,返回一个具体的方法(不分public和非public属性)

    Method[] getDeclaredMethods()    

返回该类中的所有的方法数组(不分public和非public属性)

  

 

 

 

 

 

 

 

 

 

 

 

 

3、获取类的成员变量(成员属性)

存在四种获取成员属性的方法

    Field getField(String name)    

根据变量名,返回一个具体的具有public属性的成员变量

    Field[] getFields()  

返回具有public属性的成员变量的数组

    Field getDeclaredField(String name)  

根据变量名,返回一个成员变量(不分public和非public属性)

    Field[] getDelcaredFields()    

返回所有成员变量组成的数组(不分public和非public属性)

 

 参考:

《深入理解JVM虚拟机》

Java 下高效的反射工具包 ReflectASM 使用例解 

ReflectUitls类的编写和对反射机制的解析

 Cglib源码

分类:  Java


======================================================================

http://blog.csdn.net/love_Javc_you/article/details/38081683

 深入研究Java类加载机制

分类: Java   17122人阅读  评论(2)  收藏  举报
深入研究Java类加载机制
 
类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行。
研究类加载机制的第二个目的是让程序能动态的控制类加载,比如热部署等,提高程序的灵活性和适应性。
 
一、简单过程
 
Java程序运行的场所是内存,当在命令行下执行:
java HelloWorld
命令的时候,JVM会将HelloWorld.class加载到内存中,并形成一个Class的对象HelloWorld.class。
其中的过程就是类加载过程:
1、寻找jre目录,寻找jvm.dll,并初始化JVM;
2、产生一个Bootstrap Loader(启动类加载器);
3、Bootstrap Loader自动加载Extended Loader(标准扩展类加载器),并将其父Loader设为Bootstrap Loader。
4、Bootstrap Loader自动加载AppClass Loader(系统类加载器),并将其父Loader设为Extended Loader。
5、最后由AppClass Loader加载HelloWorld类。
 
以上就是类加载的最一般的过程。
 
二、类加载器各自搜索的目录
 
为了弄清楚这个问题,首先还要看看System类的API doc文档。
 
 
相关值的描述
java.versionJava 运行时环境版本
java.vendorJava 运行时环境供应商
java.vendor.urlJava 供应商的 URL
java.homeJava 安装目录
java.vm.specification.versionJava 虚拟机规范版本
java.vm.specification.vendorJava 虚拟机规范供应商
java.vm.specification.nameJava 虚拟机规范名称
java.vm.versionJava 虚拟机实现版本
java.vm.vendorJava 虚拟机实现供应商
java.vm.nameJava 虚拟机实现名称
java.specification.versionJava 运行时环境规范版本
java.specification.vendorJava 运行时环境规范供应商
java.specification.nameJava 运行时环境规范名称
java.class.versionJava 类格式版本号
java.class.pathJava 类路径
java.library.path加载库时搜索的路径列表
java.io.tmpdir默认的临时文件路径
java.compiler要使用的 JIT 编译器的名称
java.ext.dirs一个或多个扩展目录的路径
os.name操作系统的名称
os.arch操作系统的架构
os.version操作系统的版本
file.separator文件分隔符(在 UNIX 系统中是“/”)
path.separator路径分隔符(在 UNIX 系统中是“:”)
line.separator行分隔符(在 UNIX 系统中是“/n”)
user.name用户的账户名称
user.home用户的主目录
user.dir用户的当前工作目录
 
可惜这个帮助文档并不全,直接用程序打印出来如下:
                for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { 
                        System.out.println(entry.getKey()+"\t"+entry.getValue()); 
                }
 
java.runtime.nameJava(TM) SE Runtime Environment
sun.boot.library.pathQ:\jdk6\jre\bin
java.vm.version14.0-b16
java.vm.vendorSun Microsystems Inc.
java.vendor.urlhttp://java.sun.com/
path.separator;
idea.launcher.port7532
java.vm.nameJava HotSpot(TM) Client VM
file.encoding.pkgsun.io
sun.java.launcherSUN_STANDARD
user.countryCN
sun.os.patch.levelService Pack 3
java.vm.specification.nameJava Virtual Machine Specification
user.dirE:\projects\testScanner
java.runtime.version1.6.0_14-b08
java.awt.graphicsenvsun.awt.Win32GraphicsEnvironment
java.endorsed.dirsQ:\jdk6\jre\lib\endorsed
os.archx86
java.io.tmpdirC:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\
line.separator
java.vm.specification.vendorSun Microsystems Inc.
user.variant
os.nameWindows XP
sun.jnu.encodingGBK
java.library.pathQ:\jdk6\bin;.;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;Q:\jdk6\bin;Q:\JavaFX\javafx-sdk1.2\bin;Q:\JavaFX\javafx-sdk1.2\emulator\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\MySQL Server 5.1\bin;C:\Program Files\StormII\Codec;C:\Program Files\StormII
java.specification.nameJava Platform API Specification
java.class.version50
sun.management.compilerHotSpot Client Compiler
os.version5.1
user.homed:\我的文档
user.timezone
java.awt.printerjobsun.awt.windows.WPrinterJob
idea.launcher.bin.pathC:\IDEA8\bin
file.encodingUTF-8
java.specification.version1.6
java.class.pathQ:\jdk6\jre\lib\alt-rt.jar;Q:\jdk6\jre\lib\charsets.jar;Q:\jdk6\jre\lib\deploy.jar;Q:\jdk6\jre\lib\javaws.jar;Q:\jdk6\jre\lib\jce.jar;Q:\jdk6\jre\lib\jsse.jar;Q:\jdk6\jre\lib\management-agent.jar;Q:\jdk6\jre\lib\plugin.jar;Q:\jdk6\jre\lib\resources.jar;Q:\jdk6\jre\lib\rt.jar;Q:\jdk6\jre\lib\ext\dnsns.jar;Q:\jdk6\jre\lib\ext\localedata.jar;Q:\jdk6\jre\lib\ext\sunjce_provider.jar;Q:\jdk6\jre\lib\ext\sunmscapi.jar;Q:\jdk6\jre\lib\ext\sunpkcs11.jar;E:\projects\testScanner\out\production\testScanner;C:\IDEA8\lib\idea_rt.jar
user.nameAdministrator
java.vm.specification.version1
java.homeQ:\jdk6\jre
sun.arch.data.model32
user.languagezh
java.specification.vendorSun Microsystems Inc.
awt.toolkitsun.awt.windows.WToolkit
java.vm.infomixed mode, sharing
java.version1.6.0_14
java.ext.dirsQ:\jdk6\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
sun.boot.class.pathQ:\jdk6\jre\lib\resources.jar;Q:\jdk6\jre\lib\rt.jar;Q:\jdk6\jre\lib\sunrsasign.jar;Q:\jdk6\jre\lib\jsse.jar;Q:\jdk6\jre\lib\jce.jar;Q:\jdk6\jre\lib\charsets.jar;Q:\jdk6\jre\classes
java.vendorSun Microsystems Inc.
file.separator\
java.vendor.url.bughttp://java.sun.com/cgi-bin/bugreport.cgi
sun.io.unicode.encodingUnicodeLittle
sun.cpu.endianlittle
sun.desktopwindows
sun.cpu.isalist
 
1、Bootstrap Loader(启动类加载器):加载System.getProperty("sun.boot.class.path")所指定的路径或jar。
2、Extended Loader(标准扩展类加载器ExtClassLoader):加载System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld
 
3、AppClass Loader(系统类加载器AppClassLoader):加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld
 
ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。
 
三、类加载器的特点
 
1、运行一个程序时,总是由AppClass Loader(系统类加载器)开始加载指定的类。
2、在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载。
3、Bootstrap Loader(启动类加载器)是最顶级的类加载器了,其父加载器为null.
 
四、类加载器的获取
 
很容易,看下面例子
public class HelloWorld { 
        public static void main(String[] args) { 
                HelloWorld hello = new HelloWorld(); 
                Class c = hello.getClass(); 
                ClassLoader loader = c.getClassLoader(); 
                System.out.println(loader); 
                System.out.println(loader.getParent()); 
                System.out.println(loader.getParent().getParent()); 
        } 
}
 
打印结果:
sun.misc.Launcher$AppClassLoader@19821f 
sun.misc.Launcher$ExtClassLoader@addbf1 
null 

Process finished with exit code 0
 
从上面的结果可以看出,并没有获取到ExtClassLoader的父Loader,原因是Bootstrap Loader(启动类加载器)是用C语言实现的,找不到一个确定的返回父Loader的方式,于是就返回null。
 
五、类的加载
 
类加载有三种方式:
1、命令行启动应用时候由JVM初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader.loadClass()方法动态加载
 
三种方式区别比较大,看个例子就明白了:
public class HelloWorld { 
        public static void main(String[] args) throws ClassNotFoundException { 
                ClassLoader loader = HelloWorld.class.getClassLoader(); 
                System.out.println(loader); 
                //使用ClassLoader.loadClass()来加载类,不会执行初始化块 
                loader.loadClass("Test2"); 
                //使用Class.forName()来加载类,默认会执行初始化块 
//                Class.forName("Test2"); 
                //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块 
//                Class.forName("Test2", false, loader); 
        } 
}
 
public class Test2 { 
        static { 
                System.out.println("静态初始化块执行了!"); 
        } 
}
 
分别切换加载方式,会有不同的输出结果。
 
六、自定义ClassLoader
 
为了说明问题,先看例子:
package test; 

import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

/** 
* 自定义ClassLoader 

* @author leizhimin 2009-7-29 22:05:48 
*/ 
public class MyClassLoader { 
        public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException { 
                URL url = new URL("file:/E:\\projects\\testScanner\\out\\production\\testScanner"); 
                ClassLoader myloader = new URLClassLoader(new URL[]{url}); 
                Class c = myloader.loadClass("test.Test3"); 
                System.out.println("----------"); 
                Test3 t3 = (Test3) c.newInstance(); 
        } 
}
 
public class Test3 { 
        static { 
                System.out.println("Test3的静态初始化块执行了!"); 
        } 
}
 
运行后:
---------- 
Test3的静态初始化块执行了! 

Process finished with exit code 0
 
可以看出自定义了ClassLoader myloader = new URLClassLoader(new URL[]{url});已经成功将类Test3加载到内存了,并通过默认构造方法构造了对象Test3 t3 = (Test3) c.newInstance();
 
有关ClassLoader还有很重要一点:
同一个ClassLoader加载的类文件,只有一个Class实例。但是,如果同一个类文件被不同的ClassLoader载入,则会有两份不同的ClassLoader实例(前提是着两个类加载器不能用相同的父类加载器)。


======================================================================


java 程序运行过程 简介

分类: Java   946人阅读  评论(0)  收藏  举报

    这里的java程序运行过程,是指我们编译好代码之后,在命令行开始执行java xxx命令,到java程序开始执行起来的这一过程,我们称其为运行时。

         第一步,操作系统解析我们输入的java xxx命令,根据PATH中所配置的jrd路径找的其bin目录下的java.exe程序(这个程序是用c语言写的,源码在jdk的src文件中的laucher目录下),然后再初始化一些java参数(比如classpath、虚拟机参数等)。

         第二步,java.exe程序根据上一步读入的虚拟机参数,分配内存并启动jre/bin目录下client目录或者server目录(哪个目录取决于第一步中的虚拟机参数)下的jvm.dll,java虚拟机开始启动。

         第三步,java虚拟机初始化内存,产生bootstrap classloader,这个类加载器负责加载java API(jvm+java API被称为java运行时),其实这些jar包主要分布在jre/lib下,这些我们可以通过在java命令后加-verbose:class(如下图),可见第一个被载入的java类是Object类。

[java] view plaincopy
  1. C:\Documents and Settings\nomouse>java -verbose:class  
  2. [Loaded java.lang.Object from shared objects file]  
  3. [Loaded java.io.Serializable from shared objects file]  
  4. [Loaded java.lang.Comparable from shared objects file]  
  5. [Loaded java.lang.CharSequence from shared objects file]  
  6. [Loaded java.lang.String from shared objects file]  
  7. [Loaded java.lang.reflect.GenericDeclaration from shared objects file]  
  8. [Loaded java.lang.reflect.Type from shared objects file]  
  9. [Loaded java.lang.reflect.AnnotatedElement from shared objects file]  
  10. [Loaded java.lang.Class from shared objects file]  
  11. [Loaded java.lang.Cloneable from shared objects file]  
  12. [Loaded java.lang.ClassLoader from shared objects file]  
  13. [Loaded java.lang.System from shared objects file]  
  14. [Loaded java.lang.Throwable from shared objects file]  
  15. [Loaded java.lang.Error from shared objects file]  
  16. [Loaded java.lang.ThreadDeath from shared objects file]  
  17. [Loaded java.lang.Exception from shared objects file]  
  18. [Loaded java.lang.RuntimeException from shared objects file]  
  19. [Loaded java.security.ProtectionDomain from shared objects file]  
  20. [Loaded java.security.AccessControlContext from shared objects file]  
  21. ...  

第四步,bootstrap classloader载入完java API后,还会负责载入ExtClassLoader并生成一个实例,它继承于ClassLoader类,负责载入jre/lib/ext下的jar包(所以有时候需要把servlet.jar包加进去,相当于一个不配置在classpath中就可以默认访问的公共jar目录),到这里,java虚拟机默认加载类工作完成!

第五步:java虚拟机找到我们指定的Class,加载这个类(所谓自定义类加载,是指我们自己写的java类、以及我们引入的一些第三方jar包的加载方式,只有代码中运行到类的时候才回去加载,我们可以实现自己的ClassLoader类,用来加载我们自己的类,如果我们没有实现自己的类加载器,上面说的ExtClassLoader会默认载入AppClassLoader并生成一个实例,由这个类加载器来进行加载),然后找到这个类的main方法,启动程序。



======================================================================




动态加载和卸载Java类


在开发Java服务器应用时,我们最希望开发的应用能够支持热部署,即不需要重启系统就可以用新的应用替换旧的应用。
如果使用动态语言,这些功能比较容易实现。但Java是静态语言,是否也可实现动态热部署呢?

首先,我们要深入了解一下Java的类装载(Class Loading)机制,和垃圾回收(Garbage Collection)机制。其中class loading 将负责装载新的应用包;GC将负责卸载旧的应用包。
装载新应用包的方法比较简单,只需要定制一个ClassLoader,从指定路径装载.jar文件即可。
要卸载一个ClassLoader,则必须要同时卸载通过该ClassLoader 载入的类的所有实例, 简单将ClassLoader的引用置为null并希望GC回收的做法是无效的。然而,要想统计并记录所有该ClassLoader载入的类实例是不现实的。而且,应用的装载和卸载功能,是服务器Framework的一部分,而不是应用业务功能的一部分。因此,framework也无法知道业务应用中何时载入和创建了多少对象。

解决方法还是有的。因为GC回收Heap中那些unreachable的对象,追根溯源,所有对象的创建都是由活动线程中发起的, 即所谓的Root set of references. 因此一旦活动线程结束运行,则可以说,线程中所有对象的根引用不可用了,则由根应用创建的所有对象也变为unreachable.

因此可以做如下设计:


其中Server负责service的deploy和undeploy。
其中deploy的过程可简单描述为:创建一个daemon线程,设置其context classloader为Service Jar包的ClassLoader,起动该Daemon线程。
undeploy的过程可简单描述为:停止服务(service daemon thread),设置线程context classloader为null, 等带线程彻底结束后手动执行GC来回收对象和ClassLoader.

Service接口如下:

public interface Service {

	public final static String ATTR_SERVICE_ID = "SERVICE-ID";
	public final static String ATTR_SERVICE_CLASS = "SERVICE-CLASS";

	public void install() throws ServiceException;

	public void start() throws ServiceException;

	public void stop() throws ServiceException;

	public void uninstall() throws ServiceException;

	public String getId();
}


服务管理器如下:
/**
 * 
 * @author less
 * Responsible for deploy and undeploy Services.
 */
public class ServiceManager {

	private final static HashMap<String, ServiceThread> installedServices = new HashMap<String, ServiceThread>();

	public final synchronized static String install(File jarService) throws Exception {
		MyJarLoader loader = new MyJarLoader(jarService, MyJarLoader.class.getClassLoader());
		Manifest manifest = loader.getManifest();
		Attributes attrs = manifest.getAttributes("Service");
		String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
		if (installedServices.containsKey(svcId)) {
			uninstall(svcId);
		}
		ServiceThread t = new ServiceThread();
		t.setContextClassLoader(loader);
		t.setDaemon(true);
		t.start();
		loader = null;
		return svcId;
	}

	public final static void uninstall(File jarService) throws Exception {
		MyJarLoader loader = new MyJarLoader(jarService, MyJarLoader.class.getClassLoader());
		Manifest manifest = loader.getManifest();
		Attributes attrs = manifest.getAttributes("Service");
		String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
		loader = null;
		uninstall(svcId);
	}

	public final static void uninstall(String svcId) throws Exception {
		ServiceThread t = installedServices.get(svcId);
		if (t.getStatus() == ServiceThread.Status.RUNNING) {
			t.stopService();
		}
		ServiceManager.getInstalledServices().remove(svcId);
		t.destroyService();
		t.setContextClassLoader(null);
		while (t.isAlive()) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
			}
		}
		t = null;
		System.gc();
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}
		System.gc();
	}

	static HashMap<String, ServiceThread> getInstalledServices() {
		return installedServices;
	}

}

/**
 * 
 * @author less
 * A Customer Service carrier.
 */
class ServiceThread extends Thread {

	public static enum Status {
		RUNNING, STOPPED
	}

	private Status status = Status.STOPPED;
	private Service service = null;

	public Status getStatus() {
		return this.status;
	}

	@Override
	public void run() {
		try {
			Manifest manifest = ((MyJarLoader) getContextClassLoader()).getManifest();
			Attributes attrs = manifest.getAttributes("Service");
			String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
			this.setName(svcId);
			String svcClass = attrs.getValue(Service.ATTR_SERVICE_CLASS);
			Class<Service> c = (Class<Service>) getContextClassLoader().loadClass(svcClass);
			service = c.newInstance();
			c = null;
			service.install();
			ServiceManager.getInstalledServices().put(svcId, this);
			startService();
			stopService();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void stopService() {
		if (this.status != Status.STOPPED) {
			try {
				service.stop();
				this.status = Status.STOPPED;
			} catch (Exception e) {
				e.printStackTrace();
				this.status = Status.RUNNING;
			}
		}
	}

	public void startService() {
		if (this.status == Status.STOPPED) {
			this.status = Status.RUNNING;
			try {
				service.start();
			} catch (Exception e) {
				e.printStackTrace();
				this.status = Status.STOPPED;
			}
		}
	}

	public void destroyService() {
		try {
			service.uninstall();
			service = null;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

/**
 * 
 * @author less
 * 
 */
class MyJarLoader extends JarLoader {

	public MyJarLoader(File file, ClassLoader parent) throws Exception {
		super(file, parent);
		System.out.println(this + " is created.");
	}

	@Override
	protected void finalize() throws Throwable {
		destroy();
		System.out.println(this + " is finalized.");
		super.finalize();
	}
}



示例服务代码,测试修改编译后重新部署:
/**
 * @author less
 * 
 */
public class ExampleService1 implements Service {
	private boolean bRun = true;

	@Override
	public void install() throws ServiceException {
		System.out.println("== " + this + " is installed.");
	}

	@Override
	public void start() throws ServiceException {
		System.out.println("== " + this + " is started.");

		//int i = 10000;
		int i=0;
		while (bRun) {
			//System.out.println(this + "  " + i--);
			System.out.println(this + "  " + i++);
			try {
				Thread.sleep(5000);
			} catch (InterruptedException ie) {
			}
		}
		System.out.println("== " + this + " is stopped.");
	}

	@Override
	public void stop() throws ServiceException {
		System.out.println("== Trying to stop " + this);
		bRun = false;
		Thread.currentThread().interrupt();
	}

	@Override
	public void uninstall() throws ServiceException {
		System.out.println("== " + this + " is uninstalled.");
	}

	@Override
	public String getId() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println(this + " - is finalized.");
		super.finalize();
	}
}


控制台输出如下:
Server started.
org.lucky.server.MyJarLoader@27b15692 is created.
== org.lucky.service.example.ExampleService1@4e76fba0 is installed.
== org.lucky.service.example.ExampleService1@4e76fba0 is started.
org.lucky.service.example.ExampleService1@4e76fba0  10000
org.lucky.service.example.ExampleService1@4e76fba0  9999
org.lucky.service.example.ExampleService1@4e76fba0  9998
org.lucky.service.example.ExampleService1@4e76fba0  9997
org.lucky.server.MyJarLoader@2f833eca is created.
== Trying to stop org.lucky.service.example.ExampleService1@4e76fba0
== org.lucky.service.example.ExampleService1@4e76fba0 is uninstalled.
== org.lucky.service.example.ExampleService1@4e76fba0 is stopped.
org.lucky.service.example.ExampleService1@4e76fba0 - is finalized.
org.lucky.server.MyJarLoader@27b15692 is finalized.
== org.lucky.service.example.ExampleService1@7b36a43c is installed.
== org.lucky.service.example.ExampleService1@7b36a43c is started.
org.lucky.service.example.ExampleService1@7b36a43c  0
org.lucky.service.example.ExampleService1@7b36a43c  1
org.lucky.service.example.ExampleService1@7b36a43c  2
org.lucky.service.example.ExampleService1@7b36a43c  3

由上述测试可见,通过这种方式,可以实现类动态加载和卸载以及热部署。

这个方法为Java类的动态加载/卸载提供了一个思路。由于它需要为每一个需要部署的服务起动一个线程,虽然该线程只负责装载和卸载服务,服务运行时并不消耗CPU,但会固定占用一定大小的内存。测试值为每线程约占用350k内存。因此服务多时,存在StackOverflowError的风险。

在JDK7中据说提供了anonymous classloading 机制来支持动态类加载/卸载。需要使用的同学可以关注一下。

1 楼 deepnighttwo 2011-07-11  
想卸载service1的上海,如果别的service持有哪怕一个service1中创建的对象,那也是无法卸载成功的。

所以这个方法限制非常大,如果想卸载一个service,要求service之前只能通过中间数据格式(比如xml,json)来交换数据。或者通过每个service公共的parent class loader加载的类的实例来交换数据。

如果作为一个框架放开了用的话,一旦别的service持有了另一个service创建出来的对象,那就是灾难。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于unsafe类类和类,需要先了解一些基本概念。 Java中,类分为三个阶段:、链接、初始化。其中,链接阶段又分为验证、准备、解析三个阶段。 - :查找并类的二进制数据。 - 链接: - 验证:验证被类的二进制数据的正确性。 - 准备:为类变量(static修饰的变量)分配内存并初始化为默认值。 - 解析:把类中的符号引用转换为直接引用。 - 初始化:为类的静态变量赋值,执行静态初始化块。 Java中,类因为JVM设计的限制,是不可控的。只有当一个类的所有实例都被回收,并且它的ClassLoader被回收时,才会尝试类。即一个类的ClassLoader被时,才会尝试这个类。 下面,我们将结合Unsafe类来进行类操作。 1. 使用Unsafe类类 使用Unsafe类,可以通过内存地址手动类。下面是一个Unsafe类类的例子,在该例子中,首先使用URLClassLoader从指定路径TestClass.class,然后获取TestClass.class在内存中的地址,通过Unsafe类手动TestClass.class。 ```java // TestClass.class URL url = file.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[]{url}, null); Class<?> clazz = classLoader.loadClass("TestClass"); // 获取TestClass在内存中的地址 Field classField = Unsafe.class.getDeclaredField("theUnsafe"); classField.setAccessible(true); Unsafe unsafe = (Unsafe) classField.get(null); long classAddress = unsafe.staticFieldOffset(clazz.getDeclaredField("class$0")); // 使用Unsafe类TestClass.class Class<?> loadedClass = (Class<?>) unsafe.getObject(null, classAddress); ``` 2. 使用Unsafe类类 由于Java中对类的限制,使用Unsafe类类是一件非常危险的操作,需要谨慎使用。下面是一个使用Unsafe类类的例子,在该例子中,首先使用Unsafe类手动TestClass.class,然后使用Unsafe类TestClass类。 ```java // 使用Unsafe类TestClass.class byte[] classData = getClassData(); Class<?> loadedClass = unsafe.defineClass(null, classData, 0, classData.length, null, null); // 使用Unsafe类TestClass.class Field classLoaderField = ClassLoader.class.getDeclaredField("classes"); classLoaderField.setAccessible(true); Vector<Class<?>> classes = (Vector<Class<?>>) classLoaderField.get(classLoader); classes.remove(loadedClass); Field classLoaderValueField = ClassLoader.class.getDeclaredField("classLoaderValues"); classLoaderValueField.setAccessible(true); Hashtable<Class<?>, Object> classLoaderValues = (Hashtable<Class<?>, Object>) classLoaderValueField.get(classLoader); classLoaderValues.remove(loadedClass); Class<?> classLoaderClass = Class.forName("java.lang.ClassLoader"); Method unregisterMethod = classLoaderClass.getDeclaredMethod("unregister", Class.class); unregisterMethod.setAccessible(true); unregisterMethod.invoke(classLoader, loadedClass); ``` 总的来说,在Java中手动类是一件非常危险的操作,需要谨慎使用,除非是在极端情况下才需要考虑使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值