首页编程java编程java中的类加载器有什么用?Java类加载机制

java中的类加载器有什么用?Java类加载机制

编程之家2023-10-1290次浏览

各位老铁们,大家好,今天由我来为大家分享java中的类加载器有什么用,以及Java类加载机制的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!

java中的类加载器有什么用?Java类加载机制

JVM 为什么要3个类加载器

JVM有三种类加载器:bootstrap负责加载系统类,extclassloader负责加载扩展类,appclassloader负责加载应用类。他们主要是分工不一样,各自负责不同的区域,另外也是为了实现委托模型。什么是委托模型呢,其实就是当类加载器有加载需求的时候,先请示他的父类使用父类的搜索路径来加入,如果没有找到的话,才使用自己的搜索路径来来搜索类。

当执行 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和由哪个类加载器加载没有关系。

下面的图形可以表示三者之间的关系:

java中的类加载器有什么用?Java类加载机制

BootstrapLoader<---(Extends)----AppClassLoader<---(Extends)----ExtClassLoader

这三个加载器就构成我们的 Java类加载体系。他们分别从以下的路径寻找程序所需要的类:

BootstrapLoader: sun.boot.class.path

java中的类加载器有什么用?Java类加载机制

ExtClassLoader: java.ext.dirs

AppClassLoader: java.class.path

这三个系统参量可以通过 System.getProperty()函数得到具体对应的路径。大家可以自己编程实现查看具体的路径。

Java类别载入器

1 Java的动态特性

Java的动态特性有两种,一是隐式的;另一种是显示的。隐式的(implicit)方法就是当程式设计师用到new这个Java关键字时,会让类别载入器依需求载入您所需要的类别,这种方式使用了隐式的(implicit)方法。显式的方法,又分成两种方式,一种是藉由java.lang.Class里的forName()方法,另一种则

是藉由java.lang.ClassLoader里的loadClass()方法。您可以任意选用其中一种方法。

2隐式的动态特性

在执行java文件时,只有单独的变量声明是不会载入相应的类的,只有在用new生成实例时才载入

如示例所示:

public class Main

public static void main(String args[])

{

A a1= new A();

B b1;

}

类A和B相同,如下:

public class A

{

public void print(“using A”);

}

编译后,可用java _verbose:class Main运行,察看输出结果。可以看到JVM只载入了A,而没有载入B.

另外,类的载入只在执行到new一个类时,才载入,如果没有执行到new语句,则不载入。

如://类Office

public class Office

{

public static void main(String[] args)

{

Word myword=null;

Excel myexcel=null;

if(args[0].equals("Word"))

{

myword= new Word();

myword.start();

}

if(args[0].equals("Excel"))

{

myexcel= new Excel();

myexcel.start();

}

}

}

//类Word和Excel基本相同,如下

public class Word

{

public void start()

{

System.out.println("using word");

}

}

在dos命令提示符下,输入java _verbose Office Excel可以看到JVM只载入Excel类,而不载入Word类。

3显示的动态特性

3.1 java.lang.Class里的forName()方法

在上一个Office示例中,进行如下修改:

一加入Assembly类

public interface Assembly

{

public void start();

}

二让Word和Excel类实现该接口

public class Word implements Assembly

{

public void start()

{

System.out.println("using word");

}

}

三 Office类如下所示

public class Office

{

public static void main(String[] args) throws Exception

{

java.lang.Class c= java.lang.Class.forName(args[0]);

Object o= c.newInstance();

Assembly a=(Assembly)o;

a.start();

}

}

在命令提示符下输入java _verbose Office Word输出入下:

通过上图你可以看到,interface如同class一般,会由编译器产生一个独立的类别档(.class),当类别载入器载入类别时,如果发现该类别继承了其他类别,或是实作了其他介面,就会先载入代表该介面的类别档,也会载入其父类别的类别档,如果父类别也有其父类别,也会一并优先载入。换句话说,类别载入器会依继承体系最上层的类别往下依序载入,直到所有的祖先类别都载入了,才轮到自己载入。

下面介绍一下 forName函数,如果您亲自搜寻Java 2 SDK说明档内部对於Class这个类别的说明,您可以发现其实有两个forName()方法,一个是只有一个参数的(就是之前程式之中所使用的):

public static Class forName(String className)

另外一个是需要三个参数的:

public static Class forName(String name, boolean initialize,ClassLoader loader)

这两个方法,最後都是连接到原生方法forName0(),其宣告如下:

private static native Class forName0(String name, boolean initialize, ClassLoader loader)

throws ClassNotFoundException;

只有一个参数的forName()方法,最後叫用的是:

forName0(className, true, ClassLoader.getCallerClassLoader());

而具有三个参数的forName()方法,最後叫用的是:

forName0(name, initialize, loader);

这里initialize参数指,在载入类之后是否进行初始化,对于该参数的作用可用如下示例察看:

类里的静态初始化块在类第一次被初始化时才被呼叫,且仅呼叫一次。在Word类里,加入静态初始化块

public class Word implements Assembly

{

static

{

System.out.println("word static initialization");

}

public void start()

{

System.out.println("using word");

}

}

将类Office作如下改变:

public class Office

{

public static void main(String[] args) throws Exception

{

Office off= new Office();

System.out.println("类别准备载入");

java.lang.Class c= java.lang.Class.forName(args[0],true,off.getClass().getClassLoader());

System.out.println("类别准备实体化");

Object o= c.newInstance();

Object o2= c.newInstance();

}

}

#p#副标题#e#

如果第二个参数为true则输出入下

如果为false,则输出入下:

可见,类里的静态初始化块仅在初始化时才执行,且不过初始化几次,它仅执行一次(这里有一个条件,那就是只有它是被同一个类别载入器多次载入时,才是这样,如果被不同的载入器,载入多次,则静态初始化块会执行多次)。

关于第三个参数请见下节介绍

3.2直接使用类别载入器 java.lang.ClassLoader

在Java之中,每个类别最後的老祖宗都是Object,而Object里有一个名为getClass()的方法,就是用来取得某特定实体所属类别的参考,这个参考,指向的是一个名为Class类别(Class.class)的实体,您无法自行产生一个Class类别的实体,因为它的建构式被宣告成private,这个Class类别的实体是在类别档(.class)第一次载入记忆体时就建立的,往後您在程式中产生任何该类别的实体,这些实体的内部都会有一个栏位记录着这个Class类别的所在位置。

基本上,我们可以把每个Class类别的实体,当作是某个类别在记忆体中的代理人。每次我们需要

查询该类别的资料(如其中的field、method等)时,就可以请这个实体帮我们代劳。事实上,Java的Reflection机制,就大量地利用Class类别。去深入Class类别的原始码,我们可以发现Class类别的定义中大多数的方法都是原生方法(native method)。

在Java之中,每个类别都是由某个类别载入器(ClassLoader的实体)来载入,因此,Class类别的实体中,都会有栏位记录着载入它的ClassLoader的实体(注意:如果该栏位是null,并不代表它不是由类别载入器所载入,而是代表这个类别由靴带式载入器(bootstrap loader,也有人称rootloader)所载入,只不过因为这个载入器并不是用Java所写成,是用C++写的,所以逻辑上没有实体)。

系统里同时存在多个ClassLoader的实体,而且一个类别载入器不限於只能载入一个类别,类别载入器可以载入多个类别。所以,只要取得Class类别实体的参考,就可以利用其getClassLoader()方法篮取得载入该类别之类别载入器的参考。getClassLoader()方法最後会呼叫原生方法getClassLoader0(),其宣告如下:private native ClassLoader getClassLoader0();

最後,取得了ClassLoader的实体,我们就可以叫用其loadClass()方法帮我们载入我们想要的类别,因此上面的Office类可做如下修改:

public class Office

{

public static void main(String[] args) throws Exception

{

Office off= new Office();

System.out.println("类别准备载入");

ClassLoader loader= off.getClass().getClassLoader();

java.lang.Class c= loader.loadClass(args[0]);

System.out.println("类别准备实体化");

Object o= c.newInstance();

Object o2= c.newInstance();

}

}

其输出结果同forName方法的第二个参数为false时相同。可见载入器载入类时只进行载入,不进行初始化。

获取ClassLoader还可以用如下的方法:

public class Office

{

public static void main(String[] args) throws Exception

{

java.lang.Class cb= Office.class;

System.out.println("类别准备载入");

ClassLoader loader= cb.getClassLoader();

java.lang.Class c= loader.loadClass(args[0]);

System.out.println("类别准备实体化");

Object o= c.newInstance();

Object o2= c.newInstance();

}

}

在此之前,当我们谈到使用类别载入器来载入类别时,都是使用既有的类别载入器来帮我们载

入我们所指定的类别。那麽,我们可以自己产生类别载入器来帮我们载入类别吗?答案是肯定的。

利用Java本身提供的.URLClassLoader类别就可以做到。

public class Office

{

public static void main(String[] args) throws Exception

{

URL u= new URL("file:/d:/myapp/classload/");

URLClassLoader ucl= new URLClassLoader(new URL[]{u});

java.lang.Class c= ucl.loadClass(args[0]);

Assembly asm=(Assembly)c.newInstance();

asm.start();

}

}

在这个范例中,我们自己产生.URLClassLoader的实体来帮我们载入我们所需要的类别。但是载入前,我们必须告诉URLClassLoader去哪个地方寻找我们所指定的类别才行,所以我们必须给它一个URL类别所构成的阵列,代表我们希望它去搜寻的所有位置。URL可以指向网际网路上的任何位置,也可以指向我们电脑里的档案系统(包含JAR档)。在上述范例中,我们希望URLClassLoader到d:mylib这个目录下去寻找我们需要的类别,所以指定的URL为”file:/d:/my/lib/”。其实,如果我们请求的位置是主要类别(有public static void main(String args[])方法的那个类别)的相对目录,我们可以在URL的地方只写”file:lib/”,代表相对於目前的目录。

下面我们来看一下系统为我们提供的3个类别载入器:

java.exe是利用几个基本原则来寻找Java Runtime Environment(JRE),然後把类别档(.class)直接转交给JRE执行之後,java.exe就功成身退。类别载入器也是构成JRE的其中一个重要成员,所以最後类别载入器就会自动从所在之JRE目录底下的libt.jar载入基础类别函式库。

当我们在命令列输入java xxx.class的时候,java.exe根据我们之前所提过的逻辑找到了JRE(Java Runtime Environment),接着找到位在JRE之中的jvm.dll(真正的Java虚拟机器),最後载入这个动态联结函式库,启动Java虚拟机器。虚拟机器一启动,会先做一些初始化的动作,比方说抓取系统参数等。一旦初始化动作完成之後,就会产生第一个类别载入器,即所谓的Bootstrap Loader,Bootstrap Loader是由C++所撰写而成(所以前面我们说,以Java的观点来看,逻辑上并不存在Bootstrap Loader的类别实体,所以在Java程式码里试图印出其内容的时候,我们会看到的输出为null),这个Bootstrap Loader所

做的初始工作中,除了也做一些基本的初始化动作之外,最重要的就是载入定义在sun.misc命名空间底下的Launcher.java之中的ExtClassLoader(因为是inner class,所以编译之後会变成Launcher$ExtClassLoader.class),并设定其Parent为null,代表其父载入器为BootstrapLoader。然後Bootstrap Loader再要求载入定义於sun.misc命名空间底下的Launcher.java之中的AppClassLoader(因为是inner class,所以编译之後会变成Launcher$AppClassLoader.class),并设定其Parent为之前产生的ExtClassLoader实体。

这里要请大家注意的是,Launcher$ExtClassLoader.class与Launcher$AppClassLoader.class都可能是由Bootstrap Loader所载入,所以Parent和由哪个类别载入器载入没有关系。

三个载入器的层次关系可通过运行下面的例子察看:

public class Test

{

public static void main(String[] args)

{

ClassLoader cl1= Test.class.getClassLoader();

System.out.println(cl1);

ClassLoader cl2= cl1.getParent();

System.out.println(cl2);

ClassLoader cl3= cl2.getParent();

System.out.println(cl3);

}

}

运行结果:

////////////////////////////////////////////////////////////

sun.misc.Launcher$AppClassLoader@1a0c10f

sun.misc.Launcher$ExtClassLoader@e2eec8

null

//////////////////////////////////////////////////////////

如果在上述程式中,如果您使用程式码:

cl1.getClass.getClassLoader()及cl2.getClass.getClassLoader(),您会发现印出的都是null,

这代表它们都是由Bootstrap Loader所载入。这里也再次强调,类别载入器由谁载入(这句话有点

诡异,类别载入器也要由类别载入器载入,这是因为除了Bootstrap Loader之外,其余的类别载

入器皆是由Java撰写而成),和它的Parent是谁没有关系,Parent的存在只是为了某些特殊目的,

这个目的我们将在稍後作解释。

在此要请大家注意的是,AppClassLoader和ExtClassLoader都是URLClassLoader的子类别。

由於它们都是URLClassLoader的子类别,所以它们也应该有URL作为搜寻类别档的参考,由原始码

中我们可以得知,AppClassLoader所参考的URL是从系统参数java.class.path取出的字串所决定,

而java.class.path则是由我们在执行java.exe时,利用_cp或-classpath或CLASSPATH环境变

数所决定。

#p#副标题#e#

用如下示例测试:

public class AppLoader

{

public static void main(String[] args)

{

String s= System.getProperty("java.class.path");

System.out.println(s);

}

}

/////////////////////////////////////////////////////////////////

D:myappclassloadjava AppLoader

.;D:myjavaTomcat5.0webappsaxisWEB-INFlibaxis.jar;D:myjavaTomcat5.0weba

ppsaxisWEB-INFlibcommons-logging.jar;D:myjavaTomcat5.0webappsaxisWEB-IN

Flibcommons-discovery.jar;C:oracleora81jdbclibclasses12.zip;D:myjavaJDB

CforSQLserverlibmssqlserver.jar;D:myjavaJDBCforSQLserverlibmsbase.jar;D:m

yjavaJDBCforSQLserverlibmsutil.jar;D:myjavaTomcat5.0commonlibservlet-api

.jar;D:myjavaj2sdk1.4.2_04jrelibt.jar;C:sunappserverlibj2ee.jar;D:myj

avaj2sdk1.4.2_04libjaxp.jar;D:myjavaj2sdk1.4.2_04libsax.jar;

D:myappclassloadjava-classpath.;d:myapp AppLoader

.;d:myapp

/////////////////////////////////////////////////////////////////

从这个输出结果,我们可以看出,在预设情况下,AppClassLoader的搜寻路径为”.”(目前所在目

录),如果使用-classpath选项(与-cp等效),就可以改变AppClassLoader的搜寻路径,如果没有

指定-classpath选项,就会搜寻环境变数CLASSPATH。如果同时有CLASSPATH的环境设定与

-classpath选项,则以-classpath选项的内容为主,CLASSPATH的环境设定与-classpath选项两者

的内容不会有加成的效果。

至於ExtClassLoader也有相同的情形,不过其搜寻路径是参考系统参数java.ext.dirs。

系统参数java.ext.dirs的内容,会指向java.exe所选择的JRE所在位置下的libext子目录。Java.exe使用的JRE是在系统变量path里指定的,可以通过修改path从而修改ExtCLassLoader的搜寻路径,也可以如下命令参数来更改,

java _Djava.ext.dirs=c:winnt AppLoader//注意=号两边不能有空格。-D也不能和java分开。

////////////////////////////////////////////////////////////////

D:myappclassloadjava ExtLoader

D:myjavaj2sdk1.4.2_04jrelibext

D:myappclassloadjava-Djava.ext.dirs=c:winnt ExtLoader

c:winnt

////////////////////////////////////////////////////////////////

最後一个类别载入器是Bootstrap Loader,我们可以经由查询由系统参数sun.boot.class.path得知Bootstrap Loader用来搜寻类别的路径。该路径的修改与ExtClassLoader的相同。但修改后不影响Boots

Java类加载机制

1,类的加载

每个开发人员对java.lang.ClassNotFoundExcetpion这个异常肯定都不陌生,这背后就涉及到了java技术体系中的类加载。Java的类加载机制是技术体系中比较核心的部分,虽然和大部分开发人员直接打交道不多,但是对其背后的机理有一定理解有助于排查程序中出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。

那么什么是类的加载?

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载器是Java语言的一个创新,也是Java语言流行的重要原因之一。它使得Java类可以被动态加载到Java虚拟机中并执行。类加载器从JDK1.0就出现了,最初是为了满足JavaApplet的需要而开发出来的。JavaApplet需要从远程下载Java类文件到浏览器中并执行。现在类加载器在Web容器和OSGi中得到了广泛的使用,而类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

2,类的生命周期

类加载的过程中包括有加载,验证,准备,解析,初始化五个阶段。而需要注意的是在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

加载:查找并加载类的二进制数据

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等)

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

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

相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,电脑培训发现因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

END,本文到此结束,如果可以帮助到大家,还望关注本站哦!

java中boo是什么(请问这一段java代码怎么理解,谢谢解答)java封装方法什么意思?java 封装是什么