java 类加载器有几种_Java的类加载器(ClassLoader)简介

Java的类加载器(ClassLoader)简介

ClassLoader是Java的类加载器,用于把class文件加载到JVM中,下面大概了解一下Java类加载器的概况。

一,java提供的加载器

Java提供了三个ClassLoader:

1,BootstrapClassLoader

用于加载JAVA核心类库,也就是环境变量的%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。

在JVM启动时加入-Xbootclasspath参数,可以把对应路径也加载到Bootstrap的路径列表中来,这个参数有两种用法:

1),-Xbootclasspath/a:{人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,如果有class重复,以Bootstrap默认路径下的类为准(因为是按照路径列表顺序加载的),举例:java -Xbootclasspath/a:D:\test\Test.jar

2),-Xbootclasspath/p: {人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,如果有class重复,以指定路径下的类为准,举例:java -Xbootclasspath/p:D:\test\Test.jar

2,Extention ClassLoader

扩展类加载器,加载环境变量%JRE_HOME%\lib\ext目录下的class文件

这个加载器也可以在JVM启动时使用参数改变加载的行为,参数是-D java.ext.dirs=,作用是替换Java扩展类加载器所加载的文件目录。

注意,该参数是替换而不是追加,因为这个加载器的加载路径只有一个,也就是说,%JRE_HOME%\lib\ext是扩展类加载器的默认路径,如果我们在启动时使用-Djava.ext.dirs=d:/test,那么java就不再加载%JRE_HOME%\lib\ext路径下的文件。

3,AppclassLoader

加载classpath中的class类,通过在JVM启动命令中的-classpath参数指定路径,可以指定绝对路径、相对路径、环境变量等,举例:java –classpath %CLASSPATH%

二,各种加载器之间的关系

从加载关系来说:1,BootstrapClassLoader是Extention ClassLoader的父加载器。

2,ExtentionClassLoader是AppclassLoader的父加载器。

注意,这里的父加载器并不是java语言里的父类,只是逻辑上的。

从Java语言的角度来说:1,ExtentionClassLoader对应的java类是ExtClassLoader,他的父类是java.net.URLClassLoader。

2,AppclassLoader对应的java类是AppClassLoader,他的父类也是java.net.URLClassLoader,没错,和ExtClassLoader一样。

3,BootstrapClassLoader是C++编写的,压根没有对应的java类,当然也成不了别人的父类。

ClassLoader类有getParent()方法,可以得到父加载器,一个加载器的父加载器是在他初始化的时候指定的。

AppclassLoader用getParent()方法得到的是ExtClassLoader。

ExtClassLoader用getParent()方法得到的是null。

如果我们自定义一个加载器,往往要继承ClassLoader类,此时默认的父加载器是AppClassLoader。

三,加载器的加载顺序

加载器在JVM启动时的加载顺序是:1,BootstrapClassLoader

2,ExtentionClassLoader

3,AppclassLoader

关于这个加载顺序可以参考sun.misc.Launcher类,这个类在JVM启动时初始化了各个加载器,代码如下:/**

*This class is used by the system to launch the main application.

Launcher */

public class Launcher {

private static URLStreamHandlerFactory factory = new Factory();

private static Launcher launcher = new Launcher();

private static String bootClassPath =

System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {

return launcher;

}

private ClassLoader loader;

public Launcher() {

// Create the extension class loader

ClassLoader extcl;

try {

extcl = ExtClassLoader.getExtClassLoader();

} catch (IOException e) {

throw new InternalError(

"Could not createextension class loader", e);

}

// 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 applicationclass loader", e);

}

// Also set the context class loader for the primordial thread.

Thread.currentThread().setContextClassLoader(loader);

// Finally, install a security manager if requested

String s = System.getProperty("java.security.manager");

if (s != null) {

SecurityManager sm = null;

if ("".equals(s) || "default".equals(s)) {

sm = newjava.lang.SecurityManager();

} else {

try {

sm =(SecurityManager)loader.loadClass(s).newInstance();

} catch (IllegalAccessExceptione) {

} catch (InstantiationExceptione) {

} catch (ClassNotFoundExceptione) {

} catch (ClassCastException e){

}

}

if (sm != null) {

System.setSecurityManager(sm);

} else {

throw new InternalError(

"Could not createSecurityManager: " + s);

}

}

}

……后面还有很多

}

可以看到,在Launcher的无参构造中,先是初始化了ExtClassLoader,然后初始化AppClassLoader,其实Bootstrap ClassLoader在这之前就加载完了,类中有这样一个属性:private static String bootClassPath =

System.getProperty("sun.boot.class.path");

这个就是Bootstrap ClassLoader类加载的路径,可以自己写一段代码看看这个路径是什么:System.out.println(System.getProperty("sun.boot.class.path"));

输出结果:C:\ProgramFiles\Java\jre7\lib\resources.jar;

C:\Program Files\Java\jre7\lib\rt.jar;

C:\ProgramFiles\Java\jre7\lib\sunrsasign.jar;

C:\Program Files\Java\jre7\lib\jsse.jar;

C:\Program Files\Java\jre7\lib\jce.jar;

C:\ProgramFiles\Java\jre7\lib\charsets.jar;

C:\Program Files\Java\jre7\lib\jfr.jar;

C:\Program Files\Java\jre7\classes

实际输出结果是没有换行的,我在分号处加了换行。

Launcher类中加载的ExtClassLoader和AppClassLoader这两个类的定义也是在Launcher中,源码也可以看一下

ExtClassLoader类的定义:/*

* The class loader used for loading installed extensions.

*/

static class ExtClassLoader extends URLClassLoader {

static {

ClassLoader.registerAsParallelCapable();

}

/**

* create an ExtClassLoader. The ExtClassLoader is created

* within a context that limits which files it can read

*/

public static ExtClassLoader getExtClassLoader() throws IOException

{

final File[] dirs = getExtDirs();

try {

// Prior implementations of this doPrivileged() block supplied

// aa synthesized ACC via a call to the private method

// ExtClassLoader.getContext().

return AccessController.doPrivileged(

new PrivilegedExceptionAction() {

public ExtClassLoader run() throws IOException {

int len = dirs.length;

for (int i = 0; i 

MetaIndex.registerDirectory(dirs[i]);

}

return new ExtClassLoader(dirs);

}

});

} catch (java.security.PrivilegedActionException e) {

throw (IOException) e.getException();

}

}

void addExtURL(URL url) {

super.addURL(url);

}

/*

* Creates a new ExtClassLoader for the specified directories.

*/

public ExtClassLoader(File[] dirs) throws IOException {

super(getExtURLs(dirs), null, factory);

SharedSecrets.getJavaNetAccess().

getURLClassPath(this).initLookupCache(this);

}

private static File[] getExtDirs() {

String s = System.getProperty("java.ext.dirs");

File[] dirs;

if (s != null) {

StringTokenizer st =

new StringTokenizer(s, File.pathSeparator);

int count = st.countTokens();

dirs = new File[count];

for (int i = 0; i 

dirs[i] = new File(st.nextToken());

}

} else {

dirs = new File[0];

}

return dirs;

}

private static URL[] getExtURLs(File[] dirs) throws IOException {

Vector urls = new Vector();

for (int i = 0; i 

String[] files = dirs[i].list();

if (files != null) {

for (int j = 0; j 

if (!files[j].equals("meta-index")) {

File f = new File(dirs[i], files[j]);

urls.add(getFileURL(f));

}

}

}

}

URL[] ua = new URL[urls.size()];

urls.copyInto(ua);

return ua;

}

/*

* Searches the installed extension directories for the specified

* library name. For each extension directory, we first look for

* the native library in the subdirectory whose name is the value

* of the system property os.arch. Failing that, we

* look in the extension directory itself.

*/

public String findLibrary(String name) {

name = System.mapLibraryName(name);

URL[] urls = super.getURLs();

File prevDir = null;

for (int i = 0; i 

// Get the ext directory from the URL

File dir = new File(urls[i].getPath()).getParentFile();

if (dir != null && !dir.equals(prevDir)) {

// Look in architecture-specific subdirectory first

// Read from the saved system properties to avoid deadlock

String arch = VM.getSavedProperty("os.arch");

if (arch != null) {

File file = new File(new File(dir, arch), name);

if (file.exists()) {

return file.getAbsolutePath();

}

}

// Then check the extension directory

File file = new File(dir, name);

if (file.exists()) {

return file.getAbsolutePath();

}

}

prevDir = dir;

}

return null;

}

private static AccessControlContext getContext(File[] dirs)

throws IOException

{

PathPermissions perms =

new PathPermissions(dirs);

ProtectionDomain domain = new ProtectionDomain(

new CodeSource(perms.getCodeBase(),

(java.security.cert.Certificate[]) null),

perms);

AccessControlContext acc =

new AccessControlContext(new ProtectionDomain[] { domain });

return acc;

}

}

可以看到里面的getExtDirs()方法中,获得了java.ext.dirs参数的内容,这个地址也可以打印出来看看:System.out.println(System.getProperty("java.ext.dirs"));

输出的结果:C:\Program Files\Java\jre7\lib\ext;

C:\Windows\Sun\Java\lib\ext

AppClassLoader类的定义:/**

* The class loader used for loading from java.class.path.

* runs in a restricted security context.

*/

static class AppClassLoader extends URLClassLoader {

static {

ClassLoader.registerAsParallelCapable();

}

public static ClassLoader getAppClassLoader(final ClassLoader extcl)

throws IOException

{

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

final File[] path = (s == null) ? new File[0] : getClassPath(s);

// Note: on bugid 4256530

// Prior implementations of this doPrivileged() block supplied

// a rather restrictive ACC via a call to the private method

// AppClassLoader.getContext(). This proved overly restrictive

// when loading  classes. Specifically it prevent

// accessClassInPackage.sun.* grants from being honored.

//

return AccessController.doPrivileged(

new PrivilegedAction() {

public AppClassLoader run() {

URL[] urls =

(s == null) ? new URL[0] : pathToURLs(path);

return new AppClassLoader(urls, extcl);

}

});

}

final URLClassPath ucp;

/*

* Creates a new AppClassLoader

*/

AppClassLoader(URL[] urls, ClassLoader parent) {

super(urls, parent, factory);

ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

ucp.initLookupCache(this);

}

/**

* Override loadClass so we can checkPackageAccess.

*/

public Class> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

int i = name.lastIndexOf('.');

if (i != -1) {

SecurityManager sm = System.getSecurityManager();

if (sm != null) {

sm.checkPackageAccess(name.substring(0, i));

}

}

if (ucp.knownToNotExist(name)) {

// The class of the given name is not found in the parent

// class loader as well as its local URLClassPath.

// Check if this class has already been defined dynamically;

// if so, return the loaded class; otherwise, skip the parent

// delegation and findClass.

Class> c = findLoadedClass(name);

if (c != null) {

if (resolve) {

resolveClass(c);

}

return c;

}

throw new ClassNotFoundException(name);

}

return (super.loadClass(name, resolve));

}

/**

* allow any classes loaded from classpath to exit the VM.

*/

protected PermissionCollection getPermissions(CodeSource codesource)

{

PermissionCollection perms = super.getPermissions(codesource);

perms.add(new RuntimePermission("exitVM"));

return perms;

}

/**

* This class loader supports dynamic additions to the class path

* at runtime.

*

* @see java.lang.instrument.Instrumentation#appendToSystemClassPathSearch

*/

private void appendToClassPathForInstrumentation(String path) {

assert(Thread.holdsLock(this));

// addURL is a no-op if path already contains the URL

super.addURL( getFileURL(new File(path)) );

}

/**

* create a context that can read any directories (recursively)

* mentioned in the class path. In the case of a jar, it has to

* be the directory containing the jar, not just the jar, as jar

* files might refer to other jar files.

*/

private static AccessControlContext getContext(File[] cp)

throws java.net.MalformedURLException

{

PathPermissions perms =

new PathPermissions(cp);

ProtectionDomain domain =

new ProtectionDomain(new CodeSource(perms.getCodeBase(),

(java.security.cert.Certificate[]) null),

perms);

AccessControlContext acc =

new AccessControlContext(new ProtectionDomain[] { domain });

return acc;

}

}

这个类的getAppClassLoader()方法中,获得了java.class.path参数,可以打印出来:System.out.println(System.getProperty("java.class.path"));

输出结果:D:\workspace\test\bin;

C:\Users\lk\Downloads\asm-4.2.jar;

C:\Users\lk\Desktop\dubbo-2.8.3.2.jar;

C:\Users\lk\Downloads\cglib-2.2.jar;

C:\Users\lk\Downloads\netty-3.2.5.Final.jar

都是我在classpath里面配的目录和jar包。

四,查找class和双亲委托

java的加载器在查找或加载class时,需要确认这个class是否已经被加载了,如果已经被加载了自己就不再重复加载。

类加载器查找class的方式叫做双亲委托模式,基本方法是:1,自己先查缓存,验证类是否已加载,如果缓存中没有则向上委托父加载器查询。

2,父加载器接到委托也是查自己的缓存,如果没有再向上委托。

3,直到最顶级的BootstrapClassLoader也没在缓存中找到该类,则Bootstrap ClassLoader从他自己的加载路径中查找该类,如果找不到则返回下一级加载器。

4,下一级加载器也从他自己的加载路径中查找该类,如果找不到则返回下一级加载器,直到返回最开始的加载器。

简单来说,就是从下往上查缓存,然后从上往下扫描路径。如果在其中任何一步发现已经加载了该类,都会立刻返回,不再进行后面的查找。

画个图来表示这个流程的话,应该是这样的:

图1

975a5c21555b2e3fba2cb2aa334223df.png

查找的顺序就是图上从①到⑥

五,自定义ClassLoader

自定义的ClassLoader类,一般需要满足以下条件:1,继承java.lang.ClassLoader类,或者继承他的子类比如java.net.URLClassLoader。

2,重写findClass()方法或者重写loadClass()方法,findClass()会在调用加载器的loadClass()方法时调用。

3,在findClass()中使用defineClass()方法,这个方法不需要自己实现,是父类ClassLoader的方法。这个方法的参数在后面的例子中再详解。

使用自定义的ClassLoader类,一般需要以下步骤:1,初始化ClassLoader。

2,调用ClassLoader的loadClass()方法加载目标class,参数是String类型,内容包括目标类的包名和类名,不包括”.class”,这个方法是父类ClassLoader的方法,不需要自己定义。在调用这个方法时,就会调用自己在加载器中写的findClass()方法。

3,使用加载好的类的class.newInstance()就可以得到目标类的对象。

下面举个例子

首先是目标Class,简单写一个:package classloader;

public class Test {

public void hello() {

System.out.println("hello world");

}

}

下面是重点,自定义ClassLoader:package classloader;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

public class MyClassLoader extends ClassLoader {

private String myClassPath;

public MyClassLoader(String path) {

myClassPath = path;

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

File file = new File(myClassPath, name+".class");

try {

FileInputStream is = new FileInputStream(file);

ByteArrayOutputStream bos = new ByteArrayOutputStream();

int len = 0;

try {

while ((len = is.read()) != -1) {

bos.write(len);

}

} catch (IOException e) {

e.printStackTrace();

}

byte[] data = bos.toByteArray();

is.close();

bos.close();

return defineClass(name, data, 0, data.length, null);

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return super.findClass(name);

}

}

自定义的ClassLoader可以继承ClassLoader类,并重写findClass(String name)方法,这个方法中的内容,基本就是为了最后的调用defineClass()方法做准备。

defineClass()方法在父类中有多个重载的方法,他们最终调用的是一个5个参数的defineClass()方法,这5个参数分别是:1,文件名(带”.class”)

2,class文件内容的二进制数组

3,二进制数组中表示class数据开始的下标

4,class二进制数据的长度

5,protectionDomain,标识了类的封装域的权限信息,可以没有(本例就没有),详解可参考JDK文档。

从这个方法的定义来看,似乎是可以支持一长串二进制数组,开发者只需要指定数组中代表目标Class的开始下标和长度,java就可以从中截取出目标Class的信息并装载,但我没想到有什么场景可以用到这个设定。(本例中二进制数组来源于完整的class文件,所以开始下标是0,并且java需要读取整个数组)

最后写一个类使用一下我们自定义的加载器:package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

public static void main(String[] args) {

try {

// 初始化加载器

MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

// 加载class

Class c = myLoader.loadClass("classloader.Test");

// 验证

Object obj = c.newInstance();

Method method = c.getDeclaredMethod("hello", null);

method.invoke(obj, null);

} catch (Exception e) {

e.printStackTrace();

}

}

}

运行这个类,控制台输出hello world,说明目标类加载成功。

loadClass()方法在是在ClassLoader类中定义的,方法代码如下:protected Class> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// First, check if the class has already been loaded

Class> c = findLoadedClass(name);

if (c == null) {

long t0 = System.nanoTime();

try {

if (parent != null) {

c = parent.loadClass(name, false);

} else {

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

// ClassNotFoundException thrown if class not found

// from the non-null parent class loader

}

if (c == null) {

// If still not found, then invoke findClass in order

// to find the class.

long t1 = System.nanoTime();

c = findClass(name);

// this is the defining class loader; record the stats

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

sun.misc.PerfCounter.getFindClasses().increment();

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

}

可以看到,方法首先检查这个类有没有被加载,如果没有被加载,则先去父加载器加载。如果没有父加载器,则使用Bootstrap ClassLoader加载。如果父加载器没能加载这个类,则调用findClass()方法加载。

六,重新加载class,热替换

首先是基础知识:1,java目前没有专门的API,用来卸载JVM中加载的类。

2,要卸载JVM中的类,需要该类的对象都被回收,加载该类的ClassLoader也被回收,使用该类的线程结束等条件,比较严格。

3,在java中,不同的ClassLoader可以加载同一个类,即使class文件是同一个也可以被加载。但是同一个ClassLoader不能重复加载一个类,重复加载会报错。

总的来说,在不停服务的情况下热替换class不是很靠谱,现在的java版本也根本没打算让开发者这么做。

虽然可以通过新建ClassLoader实例的方法来改变新加载的Class内容,但之前ClassLoader加载的类和对象不会被修改,什么时候能被GC回收也很不可控,玩玩可以,线上环境慎用,后续的坑很多。

还是老老实实重启比较靠谱。

不过我们依然可以做出一个山寨版的热替换功能,方案就是之前提到的新建ClassLoader实例。

首先我准备了两个目标类,分别编译成class文件

第一个:package classloader;

public class Test {

public void hello() {

System.out.println("hello world");

//        System.out.println("Are you OK");

}

}

第二个:package classloader;

public class Test {

public void hello() {

//        System.out.println("hello world");

System.out.println("Are you OK");

}

}

我把第二个类编译出的class文件单独保存,将来要替换第一个class。

然后是自己定义的ClassLoader类:package classloader;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

public class MyClassLoader2 extends ClassLoader {

private String myClassPath;

public MyClassLoader2(String path) {

myClassPath = path;

}

public Class> loadMyClass(String name){

System.out.println("重新加载:"+name);

File file = new File(myClassPath+File.separator+name.substring(0,name.indexOf(".")), name.substring(name.indexOf(".")+1,name.length())+".class");

if(!file.exists()){

return null;

}

try {

FileInputStream is = new FileInputStream(file);

ByteArrayOutputStream bos = new ByteArrayOutputStream();

int len = 0;

try {

while ((len = is.read()) != -1) {

bos.write(len);

}

} catch (IOException e) {

e.printStackTrace();

}

byte[] data = bos.toByteArray();

is.close();

bos.close();

return defineClass(name, data, 0, data.length, null);

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return null;

}

@Override

public Class> loadClass(String name) throws ClassNotFoundException {

System.out.println("loadClass():"+name);

Class> cls = null;

if (cls == null){

cls=loadMyClass(name);

}

if(cls==null){

cls = getSystemClassLoader().loadClass(name);

System.out.println("getSystemClassLoader():"+ getSystemClassLoader());

}

if (cls == null){

throw new ClassNotFoundException(name);

}

return cls;

}

}

这个自定义的加载器重写了ClassLoader类的loadClass()方法。注意,对于目标路径下的类,每次都会重新加载,没有判断重复。

在classLoader()方法中先过自己的加载器,自己的加载器必须在特定目录中存在class文件才可以加载,否则就用系统定义的加载器加载,因为重写loaderClass()方法之后,目标类所有的相关类也会用这个方法加载(比如目标类的父类java.lang.Object)。

最后是测试类:package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

public static void main(String[] args) {

//        loadClass();

loadClass2();

}

public static void loadClass(){

try {

// 初始化加载器

MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

// 加载class

Class c = myLoader.loadClass("classloader.Test");

// 验证

Object obj = c.newInstance();

Method method = c.getDeclaredMethod("hello", null);

method.invoke(obj, null);

} catch (Exception e) {

e.printStackTrace();

}

}

public static void loadClass2(){

try {

while(true){

// 初始化加载器

MyClassLoader2 myLoader = new MyClassLoader2("D:\\workspace\\test\\bin");

// 加载class

Class c = myLoader.loadClass("classloader.Test");

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

System.out.println(Class.forName("classloader.Test").getClassLoader().toString());

System.out.println();

// 验证

Object obj = c.newInstance();

Method method = c.getDeclaredMethod("hello", null);

method.invoke(obj, null);

Thread.sleep(1000);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

测试类实际上是每隔一秒钟新建一个ClassLoader的实例,并用新ClassLoader加载目标类。

在程序启动之前,编译路径下是第一个目标类的Class文件(hello world),在程序启动之后把第二个Class文件(Are you OK)替换第一个,新加载的目标类就可以调用第二个目标类的方法。

运行后输出的结果如下:loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@616affac

sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.lang.System

重新加载:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

重新加载:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

hello world

loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@170a6001

sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.lang.System

重新加载:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

重新加载:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@6ef82fe7

sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.lang.System

重新加载:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

重新加载:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@28a2f6b

sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.lang.System

重新加载:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

重新加载:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@6665e41

sun.misc.Launcher$AppClassLoader@1ddd40f3

从输出的结果可以看到,重写的loadClass()方法不但需要加载目标Test类,还要加载java.lang.Object,java.lang.System等类。

通过loadClass()方法得到的Class,调用class.getClassLoader()方法得到的加载器,就是自己定义的MyClassLoader2,每次的实例都不一样,而通过Class.forName().getClassLoader()方法得到的加载器,是AppClassLoader,每次的实例都一样。

另外,如果在测试类中只使用一个ClassLoader的实例,在循环中多次加载目标类,则会报错,代码是这样:package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

public static void main(String[] args) {

//        loadClass();

loadClass2();

}

public static void loadClass(){

try {

// 初始化加载器

MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

// 加载class

Class c = myLoader.loadClass("classloader.Test");

// 验证

Object obj = c.newInstance();

Method method = c.getDeclaredMethod("hello", null);

method.invoke(obj, null);

} catch (Exception e) {

e.printStackTrace();

}

}

public static void loadClass2(){

try {

// 初始化加载器

MyClassLoader2 myLoader = new MyClassLoader2("D:\\workspace\\test\\bin");

while(true){

// 加载class

Class c = myLoader.loadClass("classloader.Test");

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

System.out.println(Class.forName("classloader.Test").getClassLoader().toString());

System.out.println();

// 验证

Object obj = c.newInstance();

Method method = c.getDeclaredMethod("hello", null);

method.invoke(obj, null);

Thread.sleep(1000);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

程序启动后的输出是这样:loadClass():classloader.Test

重新加载:classloader.Test

loadClass():java.lang.Object

重新加载:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

classloader.MyClassLoader2@37b7a72b

sun.misc.Launcher$AppClassLoader@28d320d6

loadClass():java.lang.System

重新加载:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

loadClass():java.io.PrintStream

重新加载:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

Are you OK

loadClass():classloader.Test

重新加载:classloader.Test

Exception in thread "main"java.lang.LinkageError: loader (instance of classloader/MyClassLoader2): attempted duplicate class definition for name: "classloader/Test"

atjava.lang.ClassLoader.defineClass1(Native Method)

atjava.lang.ClassLoader.defineClass(Unknown Source)

atclassloader.MyClassLoader2.loadMyClass(MyClassLoader2.java:42)

atclassloader.MyClassLoader2.loadClass(MyClassLoader2.java:58)

atclassloader.ClassLoaderTest.loadClass2(ClassLoaderTest.java:43)

atclassloader.ClassLoaderTest.main(ClassLoaderTest.java:10)

也就是说,第二次加载Test类时报错。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值