深入解析Java中的Class Loader类加载器

类加载的过程
类加载器的主要工作就是把类文件加载到JVM中。如下图所示,其过程分为三步:

1.加载:定位要加载的类文件,并将其字节流装载到JVM中;
2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类。在该阶段,该类还处于不可用状态;
(1)验证:对加载的字节流进行验证,比如格式上的,安全方面的;
(2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类;
(3)解析:加载该类所引用的其它类,比如父类,实现的接口等。
3.初始化:对类变量进行赋值。


类加载器的层级
下图虚线以上是JDK提供的几个重要的类加载器,详细说明如下:

(1)Bootstrap Class Loader: 当启动包含主函数的类时,加载JAVA_HOME/lib目录下或-Xbootclasspath指定目录的jar包;
(2)Extention Class Loader:加载JAVA_HOME/lib/ext目录下的或-Djava.ext.dirs指定目录下的jar包。
(3)System Class Loader:加载classpath或者-Djava.class.path指定目录下的类或jar包。

需要了解的是:

1.除了Bootstrap Class Loader外,其它的类加载器都是java.lang.ClassLoader类的子类;
2.Bootstrap Class Loader不是用Java实现,如果你没有使用个性化类加载器,那么java.lang.String.class.getClassLoader()就为null,Extension Class Loader的父加载器也为null;
3.获得类加载器的几种方式:
(1)获得Bootstrap Class Loader:试图获得Bootstrap Class Loader,得到的必然是null。可以用如下方式验证下:使用rt.jar包内的类对象的getClassLoader方法,比如java.lang.String.class.getClassLoader()可以得到或者获得Extention Class Loader,再调用getParent方法获得;
(2)获得Extention Class Loader:使用JAVA_HOME/lib/ext目录下jar包内的类对象的getClassLoader方法或者先获得System Class Loader,再通过它的getParent方法获得;
(3)获得System Class Loader:调用包含主函数的类对象的getClassLoader方法或者在主函数内调用Thread.currentThread().getContextClassLoader()或者调用ClassLoader.getSystemClassLoader();
(4)获得User-Defined Class Loader:调用类对象的getClassLoader方法或者调用Thread.currentThread().getContextClassLoader();

类加载器的操作原则
1.代理原则
2.可见性原则
3.唯一性原则
4.代理原则
代理原则指的是一个类加载器在加载一个类时会请求它的父加载器代理加载,父加载器也会请求它的父加载器代理加载,如下图所示。

为什么要使用代理模式呢?首先这样可以减少重复的加载一个类。(还有其它原因吗?)

容易误解的地方:

一般会以为类加载器的代理顺序是Parent First的,也就是:

1.加载一个类时,类加载器首先检查自己是否已经加载了该类,如果已加载,则返回;否则请父加载器代理;
2.父加载器重复1的操作一直到Bootstrap Class Loader;
3.如果Bootstrap Class Loader也没有加载该类,将尝试进行加载,加载成功则返回;如果失败,抛出ClassNotFoundException,则由子加载器进行加载;
4.子类加载器捕捉异常后尝试加载,如果成功则返回,如果失败则抛出ClassNotFoundException,直到发起加载的子类加载器。
这种理解对Bootstrap Class Loader,Extention Class Loader,System Class Loader这些加载器是正确的,但一些个性化的加载器则不然,比如,IBM Web Sphere Portal Server实现的一些类加载器就是Parent Last的,是子加载器首先尝试加载,如果加载失败才会请父加载器,这样做的原因是:假如你期望某个版本log4j被所有应用使用,就把它放在WAS_HOME的库里,WAS启动时会加载它。如果某个应用想使用另外一个版本的log4j,如果使用Parent First,这是无法实现的,因为父加载器里已经加载了log4j内的类。但如果使用Parent Last,负责加载应用的类加载器会优先加载另外一个版本的log4j。

可见性原则
每个类对类加载器的可见性是不一样的,如下图所示。

扩展知识,OSGi就是利用这个特点,每一个bundle由一个单独的类加载器加载,因此每个类加载器都可以加载某个类的一个版本,因此整个系统就可以使用一个类的多个版本。

唯一性原则
每一个类在一个加载器里最多加载一次。

扩展知识1:准确地讲,Singleton模式所指的单例指的是在一组类加载器中某个类的对象只有一份。

扩展知识2:一个类可以被多个类加载器加载,每个类对象在各自的namespace内,对类对象进行比较或者对实例进行类型转换时,会同时比较各自的名字空间,比如:

Klass类被ClassLoaderA加载,假设类对象为KlassA;同时被ClassLoaderB加载,假设类对象为KlassB,那么KlassA不等于KlassB。同时ClassA的实例被cast成KlassB时会抛出ClassCastException异常。
为什么要个性化类加载器
个性化类加载器给Java语言增加了很多灵活性,主要的用途有:

1.可以从多个地方加载类,比如网络上,数据库中,甚至即时的编译源文件获得类文件;
2.个性化后类加载器可以在运行时原则性的加载某个版本的类文件;
3.个性化后类加载器可以动态卸载一些类;
4.个性化后类加载器可以对类进行解密解压缩后再载入类。

类的隐式和显式加载
隐式加载:当一个类被引用,被继承或者被实例化时会被隐式加载,如果加载失败,是抛出NoClassDefFoundError。

显式加载:使用如下方法,如果加载失败会抛出ClassNotFoundException。

cl.loadClass(),cl是类加载器的一个实例;
Class.forName(),使用当前类的类加载器进行加载。
类的静态块的执行
假如有如下类:

package cn.fengd; 

public class Dummy {
 static {
 System.out.println("Hi");
 }
}

另建一个测试类:

package cn.fengd; 

public class ClassLoaderTest { 

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

 try {
  /*
  * Different ways of loading.
  */
  Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy");
 } catch (Exception e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 } 

}

运行后效果如何呢?

  • 不会输出Hi。由此可见使用loadClass后Class类对象并没有初始化;
  • 如果在Load语句后加上c.newInstance(); 就会有Hi输出,对该类进行实例化时才初始化类对象。
  • 如果换一种加载语句Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());
  • 不会输出Hi。因为参数false表示不需要初始化该类对象;
  • 如果在Load语句后加上c.newInstance(); 就会有Hi输出,对该类进行实例化时才初始化类对象。

如果换成Class.forName("cn.fengd.Dummy");或者new Dummy()呢?

都会输出Hi。

常见问题分析:

1.由不同的类加载器加载的指定类型还是相同的类型吗?
在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。

2.在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?
Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:

//java.lang.Class.java

 publicstatic Class<?> forName(String className)throws ClassNotFoundException {

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

}

//java.lang.ClassLoader.java

// Returns the invoker's class loader, or null if none.

static ClassLoader getCallerClassLoader() {

  // 获取调用类(caller)的类型

 Class caller = Reflection.getCallerClass(3);

  // This can be null if the VM is requesting it

 if (caller == null) {

  returnnull;

 }

 // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader

 return caller.getClassLoader0();

}

//java.lang.Class.java

//虚拟机本地实现,获取当前类的类加载器
native ClassLoader getClassLoader0();

3.在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?
在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:

//摘自java.lang.ClassLoader.java
protected ClassLoader() {

  SecurityManager security = System.getSecurityManager();

  if (security != null) {

  security.checkCreateClassLoader();

  }

  this.parent = getSystemClassLoader();

  initialized = true;

}

我们再来看一下对应的getSystemClassLoader()方法的实现:

privatestaticsynchronizedvoid initSystemClassLoader() {

  //...

  sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

  scl = l.getClassLoader();

  //...

}

我们可以写简单的测试代码来测试一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

本机对应输出如下:

sun.misc.Launcher$AppClassLoader@197d257

所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:

即时用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类:

(1)<Java_Runtime_Home>/lib下的类

(2)< Java_Runtime_Home >/lib/ext下或者由系统变量java.ext.dir指定位置中的类

(3)当前工程类路径下或者由系统变量java.class.path指定位置中的类

4.在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗?
JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论:
即时用户自定义类加载器不指定父类加载器,那么,同样可以加载到<Java_Runtime_Home>/lib下的类,但此时就不能够加载<Java_Runtime_Home>/lib/ext目录下的类了。
    说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。

5.编写自定义类加载器时,一般有哪些注意点?
(1)一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑
一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题:

//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {

 public Class<?> loadClass(String name) throws ClassNotFoundException {

  returnthis.findClass(name);

 }

 protected Class<?> findClass(String name) throws ClassNotFoundException {

  //假设此处只是到工程以外的特定目录D:/library下去加载类

  具体实现代码省略

 }

}

通过前面的分析我们已经知道,用户自定义类加载器(WrongClassLoader)的默

认的类加载器是系统类加载器,但是现在问题4种的结论就不成立了。大家可以简

单测试一下,现在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工

程类路径上的类都加载不上了。

问题5测试代码一

publicclass WrongClassLoaderTest {

 publicstaticvoid main(String[] args) {

  try {

  WrongClassLoader loader = new WrongClassLoader();

  Class classLoaded = loader.loadClass("beans.Account");

  System.out.println(classLoaded.getName());

  System.out.println(classLoaded.getClassLoader());

  } catch (Exception e) {

  e.printStackTrace();

  }

 }

}

(说明:D:"classes"beans"Account.class物理存在的)

输出结果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。)

 at java.io.FileInputStream.open(Native Method)

 at java.io.FileInputStream.<init>(FileInputStream.java:106)

 at WrongClassLoader.findClass(WrongClassLoader.java:40)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

 at java.lang.ClassLoader.defineClass1(Native Method)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

 at WrongClassLoader.findClass(WrongClassLoader.java:43)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object

 at java.lang.ClassLoader.defineClass1(Native Method)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

 at WrongClassLoader.findClass(WrongClassLoader.java:43)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass(…)引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。
问题5测试二

//用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {

 protected Class<?> findClass(String name) throws ClassNotFoundException {

  //假设此处只是到工程以外的特定目录D:/library下去加载类

  具体实现代码省略

 }

}

将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下:

beans.Account

WrongClassLoader@1c78e57

这说明,beans.Account加载成功,且是由自定义类加载器WrongClassLoader加载。

这其中的原因分析,我想这里就不必解释了,大家应该可以分析的出来了。

(2)正确设置父类加载器
通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。
(3)保证findClass(String )方法的逻辑正确性
事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。

6.如何在运行时判断系统类加载器能加载哪些路径下的类?
一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;

二是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty("java.class.path")

7.如何在运行时判断标准扩展类加载器能加载哪些路径下的类?
方法之一:

try {
  URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();

  for (int i = 0; i < extURLs.length; i++) {

   System.out.println(extURLs[i]);

  }

 } catch (Exception e) {//…}

本机对应输出如下:

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar
(0)

相关推荐

  • JAVA提高第七篇 类加载器解析

    今天我们学习类加载器,关于类加载器其实和JVM有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到JVM的时候还会详细讲到类加载器,本文分为下面几个小节讲解: 一.认识类加载器 1.什么是类加载器? 所谓的类加载器可以从其作用来理解,其功能就是将classpath目录下.class文件,加载到内存中来进行一些处理,处理完的结果就是一些字节码.那是谁把这些class类加载到内存中来的呢?就是类加载器. 2.JVM中默认的类加载器有哪些? java虚拟机中可以安装多个类加载器,系统默认三个

  • java基础学习笔记之类加载器

    类加载器 java类加载器就是在运行时在JVM中动态地加载所需的类,java类加载器基于三个机制:委托,可见,单一. 把classpath下的那些.class文件加载进内存,处理后成为字节码,这些工作是类加载器做的. 委托机制指的是将加载类的请求传递给父加载器,如果父加载器找不到或者不能加载这个类,那么再加载他. 可见性机制指的是父加载器加载的类都能被子加载器看见,但是子加载器加载的类父加载器是看不见的. 单一性机制指的是一个类只能被同一种加载器加载一次. 默认类加载器 系统默认三个类加载器:

  • java 详解类加载器的双亲委派及打破双亲委派

    java 详解类加载器的双亲委派及打破双亲委派 一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能.这时就需要自己定义类加载器,每个类加载器加载各自的类库资源,以此达到资源隔离效果.在对资源的加载上可以沿用双亲委派机制,也可以打破双亲委派机制. 一.沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可.如下例子: ①先定义一个待加载的类Test,它

  • java类加载器和类反射使用示例

    一.一个命令对应一个进程. 当我们启动一个Java程序,即启动一个main方法时,都将启动一个Java虚拟机进程,不管这个进程有多么复杂.而不同的JVM进程之间是不会相互影响的.这也就是为什么说,Java程序只有一个入口--main方法,让虚拟机调用.而两个mian方法,对应的是2个JVM进程,启动的是两个不同的类加载器,操作的实际上是不同的类.故而不会互相影响. 二.类加载. 当我们使用一个类,如果这个类还未加载到内存中,系统会通过加载.连接.初始化对类进行初始化. 1.类加载:指的是将类的c

  • classloader类加载器_基于java类的加载方式详解

    基础概念 Classloader 类加载器,用来加载 Java 类到 Java 虚拟机中.与普通程序不同的是.Java程序(class文件)并不是本地的可执行程序.当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader. JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,BootstrapClassLoader是用本地代码实现

  • Java中的类加载器_动力节点Java学院整理

    从java的动态性到类加载机制 Java是一种动态语言.那么怎样理解这个"动态"呢?或者说一门语言具备了什么特性,才能称之为动态语言呢?对于java,我是这样理解的. JVM(java虚拟机)执行的不是本地机器码指令,而是执行一种称之为字节码的指令(存在于class文件中).这就要求虚拟机在真正执行字节码之前,先把相关的class文件加载到内存中.虚拟机不是一次性加载所有需要的class文件,因为它在执行的时候根本不会知道以后会用到哪些class文件.它是每用到一个类,就会在运行时&q

  • java 类加载与自定义类加载器详解

    类加载 所有类加载器,都是ClassLoader的子类. 类加载器永远以.class运行的目录为准. 读取classpath根目录下的文件有以下几种方式: 1 在Java项目中可以通过以下方式获取classspath下的文件: public void abc(){ //每一种读取方法,使用某个类获取Appclassloader ClassLoader cl = ReadFile.class.getClassLoader(); URL url = cl.getResource("a.txt&quo

  • 深入解析Java中的Class Loader类加载器

    类加载的过程 类加载器的主要工作就是把类文件加载到JVM中.如下图所示,其过程分为三步: 1.加载:定位要加载的类文件,并将其字节流装载到JVM中: 2.链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类.在该阶段,该类还处于不可用状态: (1)验证:对加载的字节流进行验证,比如格式上的,安全方面的: (2)内存分配:为该类准备内存空间来表示其属性,方法以及引用的类: (3)解析:加载该类所引用的其它类,比如父类,实现的接口等. 3.初始化:对类变量进行赋值. 类加载器

  • 实例解析Java中的构造器初始化

    1.初始化顺序 当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值. 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化. class Window { Window(int maker) { System.out.println("Window(&quo

  • 深入解析Java中反射中的invoke()方法

    先讲一下java中的反射: 反射就是将类别的各个组成部分进行剖析,可以得到每个组成部分,就可以对每一部分进行操作 反射机制应用场景:逆向代码.动态生成类框架等,使用反射机制能够大大的增强程序的扩展性. 反射的基本步骤:首先获得Class对象,然后实例化对象,获得类的属性.方法或者构造函数,最后访问属性.调用方法.调用构造函数创建对象.而invoke()方法就是用来执行指定对象的方法. 在比较复杂的程序或框架中来使用反射技术,可以简化代码提高程序的复用性. 讲的是Method类的invoke()方

  • 解析java中的condition

    一.condition 介绍及demo Condition是在java 1.5中才出现的,它用来替代传统的Object的wait().notify()实现线程间的协作,相比使用Object的wait().notify(),使用Condition的await().signal()这种方式实现线程间协作更加安全和高效.因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作. Condition是个接口,基本的方法就是await()和signal()方法:

  • 解析Java中的static关键字

    一.static关键字使用场景 static关键字主要有以下5个使用场景: 1.1.静态变量 把一个变量声明为静态变量通常基于以下三个目的: 作为共享变量使用 减少对象的创建 保留唯一副本 第一种比较容易理解,由于static变量在内存中只会存在一个副本,所以其可以作为共享变量使用,比如要定义一个全局配置.进行全局计数.如: public class CarConstants { // 全局配置,一般全局配置会和final一起配合使用, 作为共享变量 public static final in

  • 一文解析Java中的方法重写

    目录 1.含义 2.为什么要使用方法重写 3.如何使用方法重写 3.1 基本语法 3.2 具体分析 3.3 方法重写的一些小技巧 1.含义 子类继承父类后,可以在子类中书写一个与父类同名同参的方法,从而实现对父类中同名同参数的方法的覆盖,我们把这一过程叫做方法的重写(override) 2.为什么要使用方法重写 2.1 当父类的方法满足不了子类的需求的时候,需要在子类中对该方法进行重写 2.2 题目与分析 例如存在一个父类Peple,子类Chinese,父类中有一个say()方法,输出人在说话,

  • Java中过滤器 (Filter) 和 拦截器 (Interceptor)的使用

    1.过滤器 (Filter) 过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法. init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次.注意:这个方法必须执行成功,否则过滤器会不起作用. doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter. destroy(): 当容器销毁 过滤器

  • 深入解析Java中的Classloader的运行机制

    java有两种类型的classload,一种是user-defined的,一种是jvm内置的bootstrap class loader,所有user-defined的class loader都是java.lang.ClassLoader的子类. 而jvm内置的class loader有3种,分别是 Bootstrap ClassLoader, Extension ClassLoader(即ExtClassLoader),System ClassLoader(即AppClassLoader).

  • 解析Java中的定时器及使用定时器制作弹弹球游戏的示例

    在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现.下面LZ就其原理.实例以及Timer缺陷三个方面来解析java Timer定时器. 一.简介       在java中一个完整定时任务需要由Timer.TimerTask两个类来配合完成. API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务.可安排任务执行一次,或者定期重复执行.由TimerTask:Timer 安排为一次执行或重复执行的任务.

随机推荐