导航:首页 > 编程语言 > java类加载过程

java类加载过程

发布时间:2023-07-06 06:06:12

java静态资源(静态方法,静态属性)是程序一运行就加载到jvm中,还是当被调用的时候才进行加载呢

java静态资源(静态方法,静态属性)是程序一运行就加载到jvm中的。

1、类中的静态属性会被加入到类对象(也可以叫做类的模板,是类的描述) 的构造器中,静态方法也会被加入到类对象中。

2、当第一次使用类时,JVM会通过类加载器,加载类对象,从而初始化静态属性,并装入类的方法,包括静态方法和实例方法(方法不会被调用,只是加载,从这个意义上来说,静态方法和实例方法是类似的)。

3、当创建类的实例对象时,JVM会调用类的构造器,从而初始化类的属性。

(1)java类加载过程扩展阅读:

JVM 类加载机制

JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。

1、加载

加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将jsP文件转换成对应的Class类)。

2、验证

这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

3、准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。

4、解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:

CONSTANT_Class_info

CONSTANT_Field_info

CONSTANT_Method_info

等类型的常量。

5、初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。初始化阶段是执行类构造器<client>方法的过程。

❷ java类加载顺序

记住 3 条原则:
1、父类优先于子类
2、属性优先于代码块优先于构造方法
3、静态优先于非静态
因此,类加载顺序为:
父类静态变量->父类静态语句块->子类静态变量->子类静态语句块->父类普通成员变量->父类动态语句块->父类构造器->子类普通成员变量->子类动态语句块->子类构造器

❸ Java类的热替换——概念、设计与实现

对于许多关键性业务或者庞大的 Java 系统来说 如果必须暂停系统服务才能进行系统升级 既会大大影响到系统的可用性 同时也增加了系统的管理和维护成本 因此 如果能够方便地在不停止系统业务的情况下进行系统升级 则可以很好地解决上述问题 在本文中 我们将基于实例 对构建在线升级 Java 系统的基础技术和设计原则进行了深入的讲解 相信读者能够根据文中的技术构建出自己的在线升级系统来

Java ClassLoader 技术剖析

在本文中 我们将不对 Java ClassLoader 的细节进行过于详细的讲解 而是关注于和构建在线升级系统相关的基础概念 关于 ClassLoader 的详细细节许多资料可以参考 有兴趣的读者可以自行研读

要构建在线升级系统 一个重要的技术就判如是能够实现 Java 类的热替换 —— 也就是在不停止正在运行的系统的情况下进行类(对象)的升级替换 而 Java 的 ClassLoader 正是实现这项技术的基础

在 Java 中 类销数的实例化流程分为两个部分 类的加载和类的实例化 类的加载又分为显式加载和隐式加载 大家使用 new 关键字创建类实例时 其实就隐式地包含了类的加载过程 对于类的显式加载来说 比较常用的是 Class forName 其实 它们都是通过调用 ClassLoader 类的 loadClass 方法来完成类的实际加载工作的 直接调用 ClassLoader 的 loadClass 方法是另外一种不常用的显式加载类的技术

图 Java 类加载器层次结构图

ClassLoader 在加载类时有一定的层次关系和规则 在 Java 中 有四种类型的类加载器 分别为 BootStrapClassLoader ExtClassLoader AppClassLoader 以及用户自定义的 ClassLoader 这四种类加载器分别负责不同路径的类的加载 并形成了一个类加载的层次结构

BootStrapClassLoader 处于类加载器层次结构的最高层 负责 sun boot class path 路径下类的加载 默认为 jre/lib 目录下的核心 API 或 Xbootclasspath 选项指定的 jar 包 ExtClassLoader 的加载路径为 java ext dirs 默认为 jre/lib/ext 目录或者 Djava ext dirs 指定目录下的 jar 包加载 AppClassLoader 的加载路径为 java class path 默认为环境变量 CLASSPATH 中设定的值 也可以通过 classpath 选型进行指定 用户自定义 ClassLoader 可以根据用户的需要定制自己的类加载过程 在运行期进行指定类的动态实时加载

这四种类加载器的层次关系图如 图 所示 一般来说 这四种类加载器会形成一种父子关系 高层为低层的父加载器 在进行类加载时 首先会自底向上挨个检查是否已经加载了指定类 如果已经加载则直接返回该类的引用 如果到最高层也没有加载过指定类 那么会自顶向下挨个尝试加载 直到用户自定义类加载器 如果还不能成功 就会抛出异常 Java 类的加载过程如 图 所示

图 Java 类的加载过程

每个类加载器有自己的名字空间 对于同一个类加载器实例来说 名字相同的类只能存在一个 并且仅加载一次 不管该类有没有变化 下次再需要加载时 它只是从自己的缓存中直接返回已经加载过的类引用

我们编写的应用类默认情况下都是通过 AppClassLoader 进行加载的 当亏冲首我们使用 new 关键字或者 Class forName 来加载类时 所要加载的类都是由调用 new 或者 Class forName 的类的类加载器(也是 AppClassLoader)进行加载的 要想实现 Java 类的热替换 首先必须要实现系统中同名类的不同版本实例的共存 通过上面的介绍我们知道 要想实现同一个类的不同版本的共存 我们必须要通过不同的类加载器来加载该类的不同版本 另外 为了能够绕过 Java 类的既定加载过程 我们需要实现自己的类加载器 并在其中对类的加载过程进行完全的控制和管理

编写自定义的 ClassLoader

为了能够完全掌控类的加载过程 我们的定制类加载器需要直接从 ClassLoader 继承 首先我们来介绍一下 ClassLoader 类中和热替换有关的的一些重要方法

findLoadedClass 每个类加载器都维护有自己的一份已加载类名字空间 其中不能出现两个同名的类 凡是通过该类加载器加载的类 无论是直接的还是间接的 都保存在自己的名字空间中 该方法就是在该名字空间中寻找指定的类是否已存在 如果存在就返回给类的引用 否则就返回 null 这里的直接是指 存在于该类加载器的加载路径上并由该加载器完成加载 间接是指 由该类加载器把类的加载工作委托给其他类加载器完成类的实际加载

getSystemClassLoader Java 中新增的方法 该方法返回系统使用的 ClassLoader 可以在自己定制的类加载器中通过该方法把一部分工作转交给系统类加载器去处理

defineClass 该方法是 ClassLoader 中非常重要的一个方法 它接收以字节数组表示的类字节码 并把它转换成 Class 实例 该方法转换一个类的同时 会先要求装载该类的父类以及实现的接口类

loadClass 加载类的入口方法 调用该方法完成类的显式加载 通过对该方法的重新实现 我们可以完全控制和管理类的加载过程

resolveClass 链接一个指定的类 这是一个在某些情况下确保类可用的必要方法 详见 Java 语言规范中 执行 一章对该方法的描述

了解了上面的这些方法 下面我们来实现一个定制的类加载器来完成这样的加载流程 我们为该类加载器指定一些必须由该类加载器直接加载的类集合 在该类加载器进行类的加载时 如果要加载的类属于必须由该类加载器加载的集合 那么就由它直接来完成类的加载 否则就把类加载的工作委托给系统的类加载器完成

在给出示例代码前 有两点内容需要说明一下 要想实现同一个类的不同版本的共存 那么这些不同版本必须由不同的类加载器进行加载 因此就不能把这些类的加载工作委托给系统加载器来完成 因为它们只有一份 为了做到这一点 就不能采用系统默认的类加载器委托规则 也就是说我们定制的类加载器的父加载器必须设置为 null 该定制的类加载器的实现代码如下

class CustomCL extends ClassLoader {

private String basedir; // 需要该类加载器直接加载的类文件的基目录

private HashSet dynaclazns; // 需要由该类加载器直接加载的类名

public CustomCL(String basedir String[] clazns) {

super(null); // 指定父类加载器为 null

this basedir = basedir;

dynaclazns = new HashSet();

loadClassByMe(clazns);

}

private void loadClassByMe(String[] clazns) {

for (int i = ; i < clazns length; i++) {

loadDirectly(clazns[i]);

dynaclazns add(clazns[i]);

}

}

private Class loadDirectly(String name) {

Class cls = null;

StringBuffer *** = new StringBuffer(basedir);

String classname = name replace( File separatorChar) + class ;

*** append(File separator + classname);

File classF = new File( *** toString());

cls = instantiateClass(name new FileInputStream(classF)

classF length());

return cls;

}

private Class instantiateClass(String name InputStream fin long len){

byte[] raw = new byte[(int) len];

fin read(raw);

fin close();

return defineClass(name raw raw length);

}

protected Class loadClass(String name boolean resolve)

throws ClassNotFoundException {

Class cls = null;

cls = findLoadedClass(name);

if(!ntains(name) && cls == null)

cls = getSystemClassLoader() loadClass(name);

if (cls == null)

throw new ClassNotFoundException(name);

if (resolve)

resolveClass(cls);

return cls;

}

}

在该类加载器的实现中 所有指定必须由它直接加载的类都在该加载器实例化时进行了加载 当通过 loadClass 进行类的加载时 如果该类没有加载过 并且不属于必须由该类加载器加载之列都委托给系统加载器进行加载 理解了这个实现 距离实现类的热替换就只有一步之遥了 我们在下一小节对此进行详细的讲解

实现 Java 类的热替换

在本小节中 我们将结合前面讲述的类加载器的特性 并在上小节实现的自定义类加载器的基础上实现 Java 类的热替换 首先我们把上小节中实现的类加载器的类名 CustomCL 更改为 HotswapCL 以明确表达我们的意图

现在来介绍一下我们的实验方法 为了简单起见 我们的包为默认包 没有层次 并且省去了所有错误处理 要替换的类为 Foo 实现很简单 仅包含一个方法 sayHello

清单 待替换的示例类

public class Foo{

public void sayHello() {

System out println( hello world! (version one) );

}

}

在当前工作目录下建立一个新的目录 swap 把编译好的 Foo class 文件放在该目录中 接下来要使用我们前面编写的 HotswapCL 来实现该类的热替换 具体的做法为 我们编写一个定时器任务 每隔 秒钟执行一次 其中 我们会创建新的类加载器实例加载 Foo 类 生成实例 并调用 sayHello 方法 接下来 我们会修改 Foo 类中 sayHello 方法的打印内容 重新编译 并在系统运行的情况下替换掉原来的 Foo class 我们会看到系统会打印出更改后的内容 定时任务的实现如下(其它代码省略 请读者自行补齐)

public void run(){

try {

// 每次都创建出一个新的类加载器

HowswapCL cl = new HowswapCL( /swap new String[]{ Foo });

Class cls = cl loadClass( Foo );

Object foo = cls newInstance();

Method m = foo getClass() getMethod( sayHello new Class[]{});

m invoke(foo new Object[]{});

} catch(Exception ex) {

ex printStackTrace();

}

}

编译 运行我们的系统 会出现如下的打印

图 热替换前的运行结果

好 现在我们把 Foo 类的 sayHello 方法更改为

public void sayHello() {

System out println( hello world! (version o) );

}

在系统仍在运行的情况下 编译 并替换掉 swap 目录下原来的 Foo class 文件 我们再看看屏幕的打印 奇妙的事情发生了 新更改的类在线即时生效了 我们已经实现了 Foo 类的热替换 屏幕打印如下

图 热替换后的运行结果

敏锐的读者可能会问 为何不用把 foo 转型为 Foo 直接调用其 sayHello 方法呢?这样不是更清晰明了吗?下面我们来解释一下原因 并给出一种更好的方法

如果我们采用转型的方法 代码会变成这样 Foo foo = (Foo)cls newInstance(); 读者如果跟随本文进行试验的话 会发现这句话会抛出 ClassCastException 异常 为什么吗?因为在 Java 中 即使是同一个类文件 如果是由不同的类加载器实例加载的 那么它们的类型是不相同的 在上面的例子中 cls 是由 HowswapCL 加载的 而 foo 变量类型声名和转型里的 Foo 类却是由 run 方法所属的类的加载器(默认为 AppClassLoader)加载的 因此是完全不同的类型 所以会抛出转型异常

那么通过接口调用是不是就行了呢?我们可以定义一个 IFoo 接口 其中声名 sayHello 方法 Foo 实现该接口 也就是这样 IFoo foo = (IFoo)cls newInstance(); 本来该方法也会有同样的问题的 因为外部声名和转型部分的 IFoo 是由 run 方法所属的类加载器加载的 而 Foo 类定义中 implements IFoo 中的 IFoo 是由 HotswapCL 加载的 因此属于不同的类型转型还是会抛出异常的 但是由于我们在实例化 HotswapCL 时是这样的

HowswapCL cl = new HowswapCL( /swap new String[]{ Foo });

其中仅仅指定 Foo 类由 HotswapCL 加载 而其实现的 IFoo 接口文件会委托给系统类加载器加载 因此转型成功 采用接口调用的代码如下

清单 采用接口调用的代码

public void run(){

try {

HowswapCL cl = new HowswapCL( /swap new String[]{ Foo });

Class cls = cl loadClass( Foo );

IFoo foo = (IFoo)cls newInstance();

foo sayHello();

} catch(Exception ex) {

ex printStackTrace();

}

}

确实 简洁明了了很多 在我们的实验中 每当定时器调度到 run 方法时 我们都会创建一个新的 HotswapCL 实例 在产品代码中 无需如此 仅当需要升级替换时才去创建一个新的类加载器实例

在线升级系统的设计原则

在上小节中 我们给出了一个 Java 类热替换的实例 掌握了这项技术 就具备了实现在线升级系统的基础 但是 对于一个真正的产品系统来说 升级本省就是一项非常复杂的工程 如果要在线升级 就会更加复杂 其中 实现类的热替换只是最后一步操作 在线升级的要求会对系统的整体设计带来深远的影响 下面我们来谈谈在线升级系统设计方面的一些原则

在系统设计一开始 就要考虑系统的哪些部分是需要以后在线升级的 哪些部分是稳定的

虽然我们可以把系统设计成任何一部分都是可以在线升级的 但是其成本是非常高昂的 也没有必要 因此 明确地界定出系统以后需要在线升级的部分是明智之举 这些部分常常是系统业务逻辑规则 算法等等

设计出规范一致的系统状态转换方法

替换一个类仅仅是在线升级系统所要做的工作中的一个步骤 为了使系统能够在升级后正常运行 就必须保持升级前后系统状态的一致性 因此 在设计时要考虑需要在线升级的部分所涉及的系统状态有哪些 把这些状态设计成便于获取 设置和转换的 并用一致的方式来进行

明确出系统的升级控制协议

这个原则是关于系统在线升级的时机和流程控制的 不考虑系统的当前运行状态就贸然进行升级是一项非常危险的活动 因此在系统设计中 就要考虑并预留出系统在线升级的控制点 并定义清晰 明确的升级协议来协调 控制多个升级实体的升级次序 以确保系统在升级的任何时刻都处在一个确定的状态下

考虑到升级失败时的回退机制

即使我们做了非常缜密细致的设计 还是难以从根本上保证系统升级一定是成功的 对于大型分布式系统来说尤其如此 因此在系统设计时 要考虑升级失败后的回退机制

好了 本小节我们简单介绍了在线升级系统设计时的几个重要的原则 下一小节我们将给出一个简单的实例 来演示一下如何来实现一个在线升级系统

在线升级系统实例

首先 我们来简单介绍一下这个实例的结构组成和要完成的工作 在我们的例子中 主要有三个实体 一个是升级控制实体 两个是工作实体 都基于 ActiveObject 实现

升级控制实体以 RMI 的方式对外提供了一个管理命令接口 用以接收外部的在线升级命令 工作实体有两个消息队列 一个用以接收分配给它的任务(我们用定时器定时给它发送任务命令消息) 我们称其为任务队列 另一个用于和升级控制实体交互 协作完成升级过程 我们称其为控制队列 工作实体中的任务很简单 就是使用我们前面介绍的 Foo 类简单地打印出一个字符串 不过这次字符串作为状态保存在工作实体中 动态设置给 Foo 类的实例的 升级的协议流程如下

当升级控制实体接收到来自 RMI 的在线升级命令时 它会向两个工作实体的任务队列中发送一条准备升级消息 然后等待回应 当工作实体在任务队列中收到准备升级消息时 会立即给升级控制实体发送一条准备就绪消息 然后切换到控制队列等待进一步的升级指令 升级控制实体收齐这两个工作实体发来的准备就绪消息后 就给这两个工作实体的控制队列各发送一条开始升级消息 然后等待结果 工作实体收到开始升级消息后 进行实际的升级工作 也就是我们前面讲述的热替换类 然后 给升级控制实体发送升级完毕消息 升级控制实体收到来自两个工作实体的升级完毕消息后 会给这两个工作实体的控制队列各发送一条继续工作消息 工作实体收到继续工作消息后 切换到任务队列继续工作 升级过程结束

主要的代码片段如下(略去命令消息的定义和执行细节)

清单 主要的代码片段

// 升级控制实体关键代码

class UpgradeController extends ActiveObject{

int nready = ;

int nfinished = ;

Worker[] workers;

// 收到外部升级命令消息时 会触发该方法被调用

public void askForUpgrade() {

for(int i= ; i<workers length; i++)

workers[i] getTaskQueue() enqueue(new PrepareUpgradeCmd(workers[i]));

}

// 收到工作实体回应的准备就绪命令消息时 会触发该方法被调用

public void readyForUpgrade(String worker_name) {

nready++;

if(nready == workers length){

for(int i= ; i<workers length; i++)

workers[i] getControlQueue() enqueue(new

StartUpgradeCmd(workers[i]));

}

}

// 收到工作实体回应的升级完毕命令消息时 会触发该方法被调用

public void finishUpgrade(String worker_name) {

nfinished++;

if(nfinished == workers length){

for(int i= ; i<workers length; i++)

workers[i] getControlQueue() enqueue(new

ContineWorkCmd(workers[i]));

}

}

}

// 工作实体关键代码

class Worker extends ActiveObject{

UpgradeController ugc;

HotswapCL hscl;

IFoo foo;

String state = hello world! ;

// 收到升级控制实体的准备升级命令消息时 会触发该方法被调用

public void prepareUpgrade() {

switchToControlQueue();

ugc getMsgQueue() enqueue(new ReadyForUpdateCMD(ugc this));

}

// 收到升级控制实体的开始升级命令消息时 会触发该方法被调用

public void startUpgrade(String worker_name) {

doUpgrade();

ugc getMsgQueue() enqueue(new FinishUpgradeCMD(ugc this));

}

// 收到升级控制实体的继续工作命令消息时 会触发该方法被调用

public void continueWork(String worker_name) {

switchToTaskQueue();

}

// 收到定时命令消息时 会触发该方法被调用

public void doWork() {

foo sayHello();

}

// 实际升级动作

private void doUpgrade() {

hscl = new HowswapCL( /swap new String[]{ Foo });

Class cls = hscl loadClass( Foo );

foo = (IFoo)cls newInstance();

foo SetState(state);

}

}

//IFoo 接口定义

interface IFoo {

void SetState(String);

void sayHello();

}

在Foo 类第一个版本的实现中 只是把设置进来的字符串直接打印出来 在第二个版本中 会先把设置进来的字符串变为大写 然后打印出来 例子很简单 旨在表达规则或者算法方面的升级变化 另外 我们并没有提及诸如 消息超时 升级失败等方面的异常情况 这在实际产品开发中是必须要考虑的

lishixin/Article/program/Java/hx/201311/26326

❹ java解释器如何加载类

类加载次序:1、静态代码块或者静态方法->2、main方法调用到的方法
对象加载次序:1、静态代码块或者静态方法->2、非静态代码块或者非静态方法->3、对象的构造方法。
但是有一段代码没有办法解释。代码忘了,过段时间丢上来
个人感觉应该好像不大对劲,我觉得应该是:
类装载时,1、静态代码块或者静态方法被调用
然后是程序的运行,main调用到的方法会被执行,如果是新建一个对象,则
2、非静态代码块或者非静态方法->3、对象的构造方法顺序执行。

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

首先我们要分析类加载原理,java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器)引导类加载器负责加载jdk中的系统类,这种类加载器都是用c语言实现的,在java程序中没有办法获得这个类加载器,对于java程序是一个概念而已,基本上不用考虑它的存在,像String,Integer这样的类都是由引导类加载器加载器的.
扩展类加载器负责加载标准扩展类,一般使用java实现,这是一个真正的java类加载器,负责加载jre/lib/ext中的类,和普通的类加载器一样,其实这个类加载器对我们来说也不是很重要,我们可以通过java程序获得这个类加载器。
系统类加载器,加载第一个应用类的加载器(其实这个定义并不准确,下面你将会看到),也就是执行java MainClass 时加载MainClass的加载器,这个加载器使用java实现,使用的很广泛,负责加载classpath中指定的类。

类加载器之间有一定的关系(父子关系),我们可以认为扩展类加载器的父加载器是引导类加载器(当然不这样认为也是可以的,因为引导类加载器表现在java中就是一个null),不过系统类加载器的父加载器一定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器无法加载时才会自己去加载。

我们无法获得引导类加载器,因为它是使用c实现的,而且使用引导类加载器加载的类通过getClassLoader方法返回的是null.所以无法直接操作引导类加载器,但是我们可以根据Class.getClassLoader方法是否为null判断这个类是不是引导类加载器加载的,可以通过下面的方法获得引导类加载器加载的类路径(每个jar包或者文件夹对应了一个URL);
sun.misc.Launcher.getBootstrapClassPath().getURLs()
你可以直接在你的main函数中输出就可以了
System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());
得到的结果是:
[file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/classes]

其实我们是可以指定引导类加载器的类路径的,java提供了一个-Xbootclasspath参数,不过这个参数不是标准参数。
java -Xbootclasspath: 运行时指定引导类加载器的加载路径(jar文件或者目录)
java -Xbootclasspath/p:和上面的相同,不过把这个路径放到原来的路径前面
java -Xbootclasspath/a:这个就是在原引导类路径后面添加类路径。
上面我们有提过加载第一个应用类未必就是系统加载器。
如果我把这个应用类的路径放到引导类路径中,它将会被引导类加载器加载,大致这样
java -Xbootclasspath/a:myjar.jar MainClass
如果MainClass在myjar.jar中,那么这个类将会被引导类加载器加载。
如果希望看详情,使用-verbose参数,为了看的更清楚,使用重定向,大致为(windows下):
java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:\out.txt
通过这个参数我们可以实现自己的系统类,比如替换掉java.lang.Object的实现,自己可以扩展
一些方法,不过这样做似乎没有好处,因为那就不是标准了。

我们最关心的还是系统类加载器,一般都认为系统类加载器是加载应用程序第一个类的加载器,
也就是java MainClass命令中加载MainClass的类加载器,这种说法虽然不是很严谨,但基本上还是可以这样认为的,因为我们很少会改变引导类加载器和扩展类加载器的默认行为。应该说系统类加载器负责加载classpath路径中的而且没有被扩展类加载器加载的类(当然也包括引导类加载器加载的)。如果classpath中有这个类,但是这个类也在扩展类加载器的类路径,那么系统类加载器将没有机会加载它。
我们很少改变扩展类加载器的行为,所以一般你自己定义的类都是系统类加载器加载器的。

获得系统类加载器非常简单,假设MyClass是你定义的一个类
MyClass.class.getClassLoader()返回的就是系统类加载器,当然这种方法无法保证绝对正确,我们可以使用更简单而且一定正确的方式:
ClassLoader.getSystemClassLoader()获得系统类加载器。我们知道ClassLoader是一个抽象类,所以系统类加载器肯定是ClassLoader的一个子类实现。我们来看看它是什么
ClassLoader.getSystemClassLoader().getClass();
结果是class sun.misc.Lancher$AppClassLoader
可以看出这是sun的一个实现,从名字可以看出是一个内部类,目前我也没有看到这个源代码,似乎还不是很清晰:
我们在看看它的父类是什么:
ClassLoader.getSystemClassLoader().getClass().getSuperclass();
结果是:class java.net.URLClassLoader
这个是j2se的标准类,它的父类是SecureClassLoader,而SecureClassLoader是继承ClassLoader的。
现在整个关系应该很清楚,我们会看到几乎所有的ClassLoader实现都是继承URLClassLoader的。
因为系统类加载器是非常重要的,而且是我们可以直接控制的,所以我们后面还会介绍,不过先来看一下扩展类
加载器以及它们之间的关系。

扩展类加载器似乎是一个不起眼的角色,它负责加载java的标准扩展(jre/lib/ext目录下的所有jar),它其实就是一个普通的加载器,看得见摸得着的。
首先的问题是怎么知道扩展类加载器在哪里?
的确没有直接途径获得扩展类加载器,但是我们知道它是系统类加载器的父加载器,我们已经很容易的获得系统类加载器了,所以我们可以间接的获得扩展类加载器:
ClassLoader.getSystemClassLoader().getParent().getClass();
其实是通过系统类加载器间接的获得了扩展类加载器,看看是什么东西:
结果是:class sun.misc.Launcher$ExtClassLoader
这个类和系统类加载器一样是一个内部类,而且定义在同一个类中。
同样看看它的父类是什么:
ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();
可以看出结果也是class java.net.URLClassLoader
扩展类加载jre/lib/ext目录下的所有类,包括jar,目录下的所有类(目录名不一定要classes).
现在可以回答上面的问题了,你写一个HelloWorld,放到jre/lib/ext/下的某个目录
比如 jre/lib/ext/myclass/HelloWorld.class
然后在你classpath也设置一份到这个类的路径,结果执行java HelloWorld时,这个类是被扩展类加载器加载器的,可以这样证明
public static void main(String[] args){
System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass());
System.out.println("Hello World");
}
结果可以得到class sun.misc.Launcher$ExtClassLoader
当然如果你把jre/lib/ext下myclass这个目录删除,仍然可以运行,但是这样结果是
class sun.misc.Lancher$AppClassLoader
如果你不知道这个过程的话,假设在你扩展类路径下有一份classpath中的拷贝,或者是比较低的版本,当你使用新的版本时会发现没有起作用,知道这个过程你就不会觉得奇怪了。另外就是两个不同的类加载器是可以加载一个同名的类的,也就是说虽然扩展类加载器加载了某个类,系统类加载器是可以加载自己的版本的,
但是现有的实现都没有这样做,ClassLoader中的方法是会请求父类加载器先加载的,如果你自己定义类加载器完全可以修改这种默认行为,甚至可以让他没有父加载器。

这里给出一个方法如何获得扩展类加载器加载的路径:
String path=System.getProperty("java.ext.dirs");
File dir=new File(path);
if(!dir.exists()||!dir.isDirectory()){
return Collections.EMPTY_LIST;
}
File[] jars=dir.listFiles();
URL[] urls=new URL[jars.length];
for(int i=0;i<jars.length;i++){
urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0];
}
return Arrays.asList(urls);

对于扩展类加载器我们基本上不会去关心,也很少把你自己的jar放到扩展路径,大部分情况下我们都感觉不到它的存在,当然如果你一定要放到这个目录下,一定要知道这个过程,它会优先于classpath中的类。

现在我们应该很清楚知道某个类是哪个加载器加载的,并且知道为什么是它加载的,如果要在运行时获得某个类的类加载器,直接使用Class的getClassLoader()方法就可以了。

用户定义的类一般都是系统类加载器加载的,我们很少直接使用类加载器加载类,我们甚至很少自己加载类。
因为类在使用时会被自动加载,我们用到某个类时该类会被自动加载,比如new A()会导致类A自动被加载,不过这种加载只发生一次。
我们也可以使用系统类加载器手动加载类,ClassLoader提供了这个接口
ClassLoader.getSystemClassLoader().loadClass("classFullName");
这就很明确的指定了使用系统类加载器加载指定的类,但是如果该类能够被扩展类加载器加载,系统类加载器还是不会有机会的。
我们最常用的还是使用Class.forName加载使用的类,这种方式没有指定某个特定的ClassLoader,会使用调用类的ClassLoader。
也就是说调用这个方法的类的类加载器将会用于加载这个类。比如在类A中使用Class.forName加载类B,那么加载类A的类加载器将会用于加载类B,这样两个类的类加载器是同一个。

最后讨论一下如何获得某个类加载器加载了哪些类,这个似乎有一定的使用价值,可以看出哪些类被加载了。其实这个也不是很难,因为ClassLoader中有一个classes成员变量就是用来保存类加载器加载的类列表,而且有一个方法
void addClass(Class c) { classes.addElement(c);}
这个方法被JVM调用。
我们只要利用反射获得classes这个值就可以了,不过classes声明为private的,我们需要修改它的访问权限(没有安全管理器时很容易做到)
classes = ClassLoader.class.getDeclaredField("classes");
classes.setAccessible(true);
List ret=(List) classes.get(cl); //classes是一个Vector
可惜的是对于引导类加载器没有办法获得加载的类,因为它是c实现的,在java中很难控制

阅读全文

与java类加载过程相关的资料

热点内容
遇见不安全网站怎么办 浏览:251
哪个app有庆余年电视剧 浏览:420
iphone5s视频时很黑 浏览:601
js获取端口号 浏览:347
手机短息发的链接病毒苹果手机 浏览:724
win10专业忘记家庭组密码 浏览:176
南宁applestore几楼 浏览:296
java字符串怎么初始化 浏览:349
医美哪个app好 浏览:768
代码编程和机器人编程哪个好 浏览:875
90版本男法 浏览:796
win10桌面字体难看 浏览:848
三菱fx5u支持哪些编程 浏览:7
优酷在文件夹在哪里 浏览:91
趣打印的数据文件是什么 浏览:871
linuxjava程序 浏览:483
苹果6splus数据线图片 浏览:98
苹果官网数据如何恢复 浏览:251
spss怎么拆分数据 浏览:306
photoshop复位所有工具 浏览:492

友情链接