描述一下JVM加载class文件的原理机制?

解决方案 »

  1.   

    此回复为自动发出,仅用于显示而已,并无任何其他特殊作用
    楼主【sodarkdays】截止到2008-06-29 04:42:57的历史汇总数据(不包括此帖):
    发帖数:1                  发帖分:100                
    结贴数:0                  结贴分:0                  
    未结数:1                  未结分:100                
    结贴率:0.00  %            结分率:0.00  %            
    如何结贴请参考这里:http://topic.csdn.net/u/20080501/09/ef7ba1b3-6466-49f6-9d92-36fe6d471dd1.html
      

  2.   

    Java 语言是一种具有动态性的解释型编程语言,当指定程序运行的时候, Java 虚拟机就将编译生成的 . class 文件按照需求和一定的规则加载进内存,并组织成为一个完整的 Java 应用程序。 Java 语言把每个单独的类 Class 和接口 Implements 编译成单独的一个 . class 文件,这些文件对于 Java 运行环境来说就是一个个可以动态加载的单元。正是因为 Java 的这种特性,我们可以在不重新编译其它代码的情况下,只编译需要修改的单元,并把修改文件编译后的 . class 文件放到 Java 的路径当中, 等到下次该 Java 虚拟机器重新激活时,这个逻辑上的 Java 应用程序就会因为加载了新修改的 .class 文件,自己的功能也做了更新,这就是 Java 的动态性。下面用一个简单的例子让大家对 Java 的动态加载有一个基本的认识:class TestClassA{public void method(){         System.out.println("Loading ClassA");}}public class ClassLoaderTest {public static void main(String args[]){         TestClassA testClassA = new TestClassA();         testClassA.method();}}编译后输入命令: java -verbose:class ClassLoaderTest ,执行文件。输出结构如图 (1)
    图( 1 )从运行结果我们可以看到, JRE ( JavaRuntime Environment )首先加载 ClassLoaderTest 文件,然后再加载 TestClassA 文件,从而实现了动态加载。  1. 预先加载与依需求加载Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE 的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。图( 2 )我们可以看到多个基础类被加载, java.lang.Object,java.io.Serializable 等等。
    图( 2 )相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。在这里还有一点需要说明的是, JRE 的依需求加载究竟是在什么时候把类加载进入内部的呢?我们在定义一个类实例的时候,比如 TestClassA testClassA ,这个时候 testClassA 的值为 null ,也就是说还没有初始化,没有调用 TestClassA 的构造函数,只有当执行 testClassA = new TestClassA() 以后, JRE 才正真把 TestClassA 加载进来。  2. 隐式加载和显示加载Java 的加载方式分为隐式加载( implicit )和显示加载( explicit ),上面的例子中就是用的隐式加载的方式。所谓隐式加载就是我们在程序中用 new 关键字来定义一个实例变量, JRE 在执行到 new 关键字的时候就会把对应的实例类加载进入内存。隐式加载的方法很常见,用的也很多, JRE 系统在后台自动的帮助用户加载,减少了用户的工作量,也增加了系统的安全性和程序的可读性。相对于隐式加载的就是我们不经常用到的显示加载。所谓显示加载就是有程序员自己写程序把需要的类加载到内存当中,下面我们看一段程序:class TestClass{public void method(){         System.out.println("TestClass-method");}}  public class CLTest {public static void main(String args[]) {         try{                Class c = Class.forName("TestClass");                TestClass object = (TestClass)c.newInstance();                object.method();         }catch(Exception e){                e.printStackTrace();         }}}我们通过 Class 类的 forName (String s) 方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化。事实上 Class 类还很多的功能,这里就不细讲了,有兴趣的可以参考 JDK 文档。Class 的 forName() 方法还有另外一种形式: Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要加载类的名称, flag 表示在调用该函数加载类的时候是否初始化静态区, classloader 表示加载该类所需的加载器。forName (String s) 是默认通过 ClassLoader.getCallerClassLoader() 调用类加载器的,但是该方法是私有方法,我们无法调用,如果我们想使用 Class forName(String s, boolean flag, ClassLoader classloader) 来加载类的话,就必须要指定类加载器,可以通过如下的方式来实现:Test test = new Test();//Test 类为自定义的一个测试类;ClassLoader cl = test. getClass().getClassLoader();                          // 获取 test 的类装载器;Class c = Class.forName("TestClass", true, cl);因为一个类要加载就必需要有加载器,这里我们是通过获取加载 Test 类的加载器 cl 当作加载 TestClass 的类加载器来实现加载的。  3. 自定义类加载机制之前我们都是调用系统的类加载器来实现加载的,其实我们是可以自己定义类加载器的。利用 Java 提供的 java.net.URLClassLoader 类就可以实现。下面我们看一段范例:     try{                URL url = new URL("file:/d:/test/lib/");                URLClassLoader urlCL = new URLClassLoader(new URL[]{url});                Class c = urlCL.loadClass("TestClassA");                TestClassA object = (TestClassA)c.newInstance();                object.method();         }catch(Exception e){                e.printStackTrace();         }我们通过自定义的类加载器实现了 TestClassA 类的加载并调用 method ()方法。分析一下这个程序:首先定义 URL 指定类加载器从何处加载类, URL 可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统 ( 包含 JAR 文件 ) 。上述范例当中我们从 file:/d:/test/lib/ 处寻找类;然后定义 URLClassLoader 来加载所需的类,最后即可使用该实例了。  4. 类加载器的阶层体系讨论了这么多以后,接下来我们仔细研究一下 Java 的类加载器的工作原理:当执行 java ***.class 的时候, java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。下面的图形可以表示三者之间的关系:父类父类载入载入BootstrapLoader         PARENTAppClassLoaderPARENTExtClassLoader这三个加载器就构成我们的 Java 类加载体系。他们分别从以下的路径寻找程序所需要的类:BootstrapLoader : sun.boot.class.pathExtClassLoader:       java.ext.dirsAppClassLoader:       java.class.path这三个系统参量可以通过 System.getProperty() 函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。  5. 总结了解 Java 的类加载机制对我们熟练灵活运用 Java 语言,提高程序的运行效率有着非常重要的作用,知其然也要知其所以然,这样才能从整体提高程序的质量。
    以上是个人为了毕业要发表的一篇论文,没有什么深度,下面再继续讨论一点关于ClassLoader的一定东东:public class ClassLoaderTest1{
    private ClassLoaderTest2 test = null;
    public ClassLoaderTest1(){
       test = new ClassLoaderTest2();
    }
    public void method(){
       System.out.println("Loading ClassA");
    }
    }
    class ClassLoaderTest2{
    public ClassLoaderTest2(){
      
    }
    public void method(){
       System.out.println("Loading ClassA");
    }
    }测试程序:
    URL url = null;
    try {
        url = new URL("file:/E:/JAVA/MyProject/string/");
       } catch (MalformedURLException e) {
        e.printStackTrace();
       }
       URLClassLoader cl = new URLClassLoader(new URL[]{url});
       URLClassLoader cl1 = new URLClassLoader(new URL[]{url});
             try {
        Class tempClass = cl.loadClass("ClassLoaderTest1");
        Class tempClass2 = cl.loadClass("ClassLoaderTest2");
        Object test = tempClass.newInstance();
        System.out.println(tempClass.getClassLoader());
        System.out.println(tempClass2.getClassLoader());
       } catch (Exception e) {
        e.printStackTrace();
       }当ClassLoaderTest1,ClassLoaderTest2在当前目录和E:/JAVA/MyProject/string/都存在的时候输出为sun.misc.Launcher$AppClassLoader@1050169
      sun.misc.Launcher$AppClassLoader@1050169
    即都是被AppClassLoader加载的, 即使在E:/JAVA/MyProject/string/下面也存在.当ClassLoaderTest1,ClassLoaderTest2只在E:/JAVA/MyProject/string/下存在的时候输出为
    java.net.URLClassLoader@480457
    java.net.URLClassLoader@1a7bf11
    即都是被自定义的加载器加载的,并且也可以Object test = tempClass.newInstance();下面一的是最关键的,因为ClassLoaderTest1需要用到ClassLoaderTest2,如果ClassLoaderTest2被AppClassLoader加载,而ClassLoaderTest1是被自定义的类加载器加载,就会出现如下错误:java.lang.IllegalAccessError: tried to access class ClassLoaderTest2 from class ClassLoaderTest1
    at ClassLoaderTest1.<init>(ClassLoaderTest1.java:6)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
    at java.lang.Class.newInstance0(Class.java:308)
    at java.lang.Class.newInstance(Class.java:261)
    at ClassLoaderTest.main(ClassLoaderTest.java:43)
      

  3.   

    ClassLoader 其实就是读取文件,按照CLASSPATH配置的顺序。
    然后解析后转化为类。
      

  4.   

    JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。
      

  5.   

    先顶一个,言简意赅PS:我把你的签名读成“老婆竹”了,想着你好V5啊
    - -!
      

  6.   

    http://kylinsoong.javaeye.com/blog/753834
      

  7.   

    昨天没事干,刚看了一下jvm classloader
      

  8.   

    虚拟机加载class就分为三步,装在、连接、初始化。
      

  9.   

    这个问题 推介你看看  孙卫琴的 <JAVA面向对象编程>里面有你想要的答案
      

  10.   

      我建议你看深入java虚拟机,里面绝对有你要的东西!也能帮助你对java有个更高的认识!
      

  11.   

    此回复为自动发出,仅用于显示而已,并无任何其他特殊作用
    楼主【sodarkdays】截止到2008-06-29 04:42:57的历史汇总数据(不包括此帖):
    发帖数:1 发帖分:100  
    结贴数:0 结贴分:0  
    未结数:1 未结分:100  
    结贴率:0.00 % 结分率:0.00 %  
    如何结贴请参考这里:http://topic.csdn.net/u/20080501/09/ef7ba1b3-6466-49f6-9d92-36fe6d471dd1.html
      

  12.   

    希望对您有帮助
    http://blog.csdn.net/leisore/article/details/6863812
      

  13.   

    JVM
    类的加载、连接、初始化:
    加载:查找并加载类的二进制数据连接:
    验证:确保被加载的类的正确性
    准备:为类的静态变量分配内存,并将其初始化我默认值
    解析:把类中的符号引用转换为直接引用
    初始化:为类的静态变量赋予正确的初始值Java程序对类的主动使用:
    — 创建类的实例
    — 访问某个类或接口的静态变量,或者对该静态变量赋值
    — 调用类的静态方法
    — 反射(如Class.forName(xx))
    — 初始化一个类的子类
    — Java须立即启动时被标明启动类的类 Java Test
    这时Test就是被标明启动类的类。
    以上就是主动使用的情况。所有的java虚拟机实现必须在每个类或接口首次
    主动使用时才会初始化他们。除了这六种其他的都是被动使用。加载.class文件的几种方式:
    — 从本地系统中直接加载
    — 从网络下载.class文件
    — 从zip,jar压缩包中加载
    — 从专有数据库中提取.class文件
    — 将Java源文件动态编译为.class文件类加载的最终产品是位于堆中的class对象。
    有两种类型的类加载器.自带的加载器:
    — 根类加载器(BootStrap) c++语言写的,我们无法获取
    — 扩展类加载器(Extension)Java实现
    — 系统类加载器(system)应用加载器 Java实现用户定义的了加载器:
    — Java.lang.ClassLoader的子类
    — 用户可以定制类的加载方式
    Public abstract class ClassLoader类加载器并不需要等待某个类首次使用时才去加载它们,注意这里不是初始化。LinkageError类加载后,就将进入连接阶段。连接就是就是将已经读入到内存的二进制数据合并到虚拟机的环境之中。类的验证的内容
    — 类文件的结构检查
    — 语义检查
    — 字节码的验证
    — 二进制兼容性的验证程序分析:
    <code>
    package com.cn.dai;class Singleton
    {
    private static Singleton singleton = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    private Singleton()
    {
    counter1++;
    counter2++;
    }
    public static Singleton getInstance()
    {
    return singleton;
    }
    }
    public class MyTest
    {
    public static void main(String[] args)
    {
    Singleton singleton = Singleton.getInstance();
    System.out.println("counter1 = " + singleton.counter1);
    System.out.println("counter2 = " + singleton.counter2);
    }
    }
    </code>
    Output: 1,0
    对比:
    <code>
    package com.cn.dai;class Singleton
    {
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton singleton = new Singleton();
    private Singleton()
    {
    counter1++;
    counter2++;
    }
    public static Singleton getInstance()
    {
    return singleton;
    }
    }
    public class MyTest
    {
    public static void main(String[] args)
    {
    Singleton singleton = Singleton.getInstance();
    System.out.println("counter1 = " + singleton.counter1);
    System.out.println("counter2 = " + singleton.counter2);
    }
    }
    Output:1,1这两段程序的不同之处只在于一句代码的位置不同,而输出的结果确截然不同,分析其原理。
    对于第一段程序。一开始JVM加载类Singleton,并没有初始化,这个类的成员变量的取值分别人默认值,即 singleton = null;count1 = 0; count2 = 0;现在使用了Singleton的static成员,这是对这个类的一次主动使用,所以这个类会被初始化,程序从上至下一次执行,会先new 一个对象,当然有执行构造方法,这是count1,count2的值都会加1,变成1,下面两句public static int counter1; public static int counter2 = 0;第一句没有赋初值,所以counter1的值是1,第二句counter会被从新赋值为0,所以输出的结果是1,0.
    第二段程序是类似的,不管是public static int counter2 = 0;还是构造方法中的counter1++;counter2++;都是在类初始化的时候才执行的。
    编译常量:
    <code>
    package com.cn.dai;import java.util.Random;class FinalTest2
    {
    public static final int x = new Random().nextInt(100);

    static
    {
    System.out.println("FinalTest2 static block");
    }
    }public class Test3
    {
    public static void main(String[] args)
    {
    System.out.println(FinalTest2.x);
    }
    }</code>
    Output:FinalTest2 static block
    2(一个0~99的随机数)对比:<code>
    package com.cn.dai;import java.util.Random;class FinalTest2
    {
    public static final int x = 6/3;

    static
    {
    System.out.println("FinalTest2 static block");
    }
    }public class Test3
    {
    public static void main(String[] args)
    {
    System.out.println(FinalTest2.x);
    }
    }</code>Output: 5(一个随机数)这两段程序中的FinalTest2一个初始化了,一个没有,这是为什么呢?6/3是一个编译常量,在编译的时候就已经确定值了。这是不会导致类的初始化,前一段程序,public static final int x = new Random().nextInt(100);并不能在编译的时候就赋初值,类只有在加载的时候才能赋予初值。这里我们可以发现final的变量的初始值不能是系统加载是赋予默认值就行了,它必须有我们给它一个值,如果我们给它的值,在编译的时候就可以确定下来,那么在类加载的时候给它赋的值就不是默认值了,而是这个编译时就可以确定的值。如果在编译的时候不能确定,那么就必须在初始化的时候赋予一个初始值,如果没有这个初始的赋值操作,那么编译器就会报错的。一个final的变量,不是说任何时期都会有一个初始值。在类加载的时候它可以有一个初始值,也可以没有(这时编译器会确定后面是否有赋初值的操作,如果没有就会报错)。final 变量的初始化:可以在函数块或者构造函数中去初始化,因为我们要生成一个对象那么函数块和构造方法是必须的。而且初始化只能而这选一,不然会出现The final field i may already have been assigned。
    如果要在构造方法冲去初始化final变量,那么每一个构造方法都必须有初始化操作。当然最直接的办法就是直接在变量申明后面赋值。Static final 变量的初始化,只能是直接赋值,或者是在静态代码块中赋予初值。对于Interface,我们知道interface中是可以有变量的,但是它里面的变量默认是public static final的,而interface中是不能有代码块或者静态代码块,所以这时候我们定义接口时,如果里面有成员变量,那么只能是直接赋值。
    <code>
    interface A {
    int a = 9;//( 前面的修饰符only public, static & final are permitted,不写的话默认就是这些合起来)

    }
    </code>其实通过编译器报的这句错误The interface A cannot define an initializer,我们可以发现代码块或者静态代码块的作用就是拿来初始化变量的,initializer。 只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。
    <code>
    package com.cn.dai;class Parent3
    {
    static int a = 3;

    static
    {
    System.out.println("Parent3 static block");
    }

    static void doSomething()
    {
    System.out.println("do something");
    }
    }class Child3 extends Parent3
    {
    static
    {
    System.out.println("Child3 static block");
    }
    }public class Test6
    {
    public static void main(String[] args)
    {
    System.out.println(Child3.a);

    Child3.doSomething();
    }
    }
    </code>
    这段代码Child3是不会被初始化的。类加载器******
    Java自带的类加载:
    — 根类加载器(BootStrap) c++语言写的,我们无法获取
    — 扩展类加载器(Extension)Java实现
    — 系统类加载器(system)应用加载器 Java实现
    我们也可以自己定义一个自己定义的类加载。Class类(基因类):它有getClassLoader()方法,可以得到一个类的加载器。
    API文档的一段话:
    Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。 
    Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
    从这段话可以看出,我们可以得到一个我们是不能生成一个Class对象的,因为它的构造方法是private的,但是我们可以得到一个Class ,有三种方法可以得到Class。Class.forName(“全名”);obj.class;obj.getClass();。Class与ClassLoader是双向的关联关系。你知道我,我也知道你,Class与对象之间是单向的,对象知道Class,但是Class却不知道Object,Class是Object的一面镜子。JVM的类加载机制是采用叫父委托机制来实现的。当一个类加载器要去加载一个类,那么他会委托他的父亲来加载,一直向上,知道父加载器不能加载为止。下面来自定义一个类加载器:
    <code>
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Field;public class MyClassLoader extends ClassLoader { private String name;
    private String path = "d:\\";
    private final String fileType = ".class"; public MyClassLoader(String name) {
    super();
    this.name = name;
    } public MyClassLoader(ClassLoader parent, String name) {
    super(parent);
    this.name = name;
    } public String toString() {
    return this.name;
    } public String getPath() {
    return path;
    } public void setPath(String path) {
    this.path = path;
    } public Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] data = loadClassData(name);
    return this.defineClass(name, data, 0, data.length);
    } private byte[] loadClassData(String name) {
    InputStream is = null;
    ByteArrayOutputStream baos = null; byte[] data;
    try {
    name = name.replace(".", "\\");
    is = new FileInputStream(new File(path + name + fileType));
    baos = new ByteArrayOutputStream();
    int ch = 0;
    while (-1 != (ch = is.read())) {
    baos.write(ch);
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    try {
    is.close();
    baos.close();
    } catch (IOException e) {
    e.printStackTrace();
    } }
    data = baos.toByteArray();
    return data;
    } public static void main(String[] args) throws Exception {
    MyClassLoader loader1 = new MyClassLoader("loader1");
    loader1.setPath("D:\\zzz\\server\\");
    MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
    loader2.setPath("D:\\zzz\\client\\");

    MyClassLoader loader3 = new MyClassLoader(loader2, "loader3");
    loader3.setPath("D:\\zzz\\other\\"); MyClassLoader loader4 = new
    MyClassLoader(null, "loader3"); loader3.setPath("D:\\zzz\\sys\\");



    testLoader(loader2);
    testLoader(loader3); } public static void testLoader(ClassLoader loader) {
    Class<?> clazz = null;
    Object obj = null;
    try {
    clazz = loader.loadClass("Sample");
    obj = clazz.newInstance();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
    }}</code>
    <code>
    public class Dog {
    public Dog() {
    System.out.println("Dog is loaded by:" + this.getClass().getClassLoader());
    }
    }
    </code><code>
    public class Sample {
    public int v1 = 1;
    public Sample() {
    System.out.println("Sample is loaded by:" + this.getClass().getClassLoader());
    new Dog();
    }
    }</code>在编写一个自定义的类时应该注意:
    1. 每一个自定义的类加载器都应该继承至ClassLoader这个类,这个类有两个构造方法,第一个ClassLoader(),这样表示父加载器为系统类加载器。ClassLoader()指定父加载器。
    2. 我们要重写public Class<?> findClass(String name)这个方法,name是传进来的集体的类的全名,例如:com.cn.dai.Test或者默认包Test。首先我们要得到.class 文件的byte数组,然后用这个方法this.defineClass(name, data, 0, data.length);就可以得到该二进制文件对应的Class。
    最后我们自己定义的类加载器加载的Class可以卸载,如果是其他的三种类加载器加载的Class会在程序运行期间一直存在。不会被卸载掉。这里我们可以想到我们在类中申明的static变量会一经初始化,就会在程序运行的整个过程中不会被卸载掉,会一直存在。回想单例模式,一经生成一个对象,那么我们每次得到的就会使同一个对象也就可以理解了。 Author: daijope
    2011/8/14 20:27
      

  14.   

    1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的, 
    类装载器所做的工作实质是把类文件从硬盘读取到内存中 2.java中的类大致分为三种: 
        1.系统类 
        2.扩展类 
        3.由程序员自定义的类 3.类装载方式,有两种 
        1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中, 
        2.显式装载, 通过class.forname()等方法,显式加载需要的类