【深入Java虚拟机】之四:类加载机制

1,从Java虚拟机的角度,只存在两种不同的类加载器:

1,启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。
2,其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,
这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。

2,从Java开发人员的角度来看,类加载器可以大致划分为以下三类:

,启动类加载器:Bootstrap ClassLoader,跟上面相同。
它负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,
并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。
启动类加载器是无法被Java程序直接引用的。
,扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,
它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
,应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,
它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,
如果应用程序中没有自定义过自己的类加载器,
一般情况下这个就是程序中默认的类加载器。

3,如果编写了自己的ClassLoader,需要做到如下几点:

JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件(不包括URLClassLoader)

 )在执行非置信代码之前,自动验证数字签名。
)动态地创建符合用户特定需要的定制化构建类。
)从特定的场所取得java class,例如数据库中和网络中。

事实上当使用Applet的时候,就用到了特定的ClassLoader,因为这时需要从网络上加载java class,并且要检查相关的安全信息,

应用服务器也大都使用了自定义的ClassLoader技术。

4,类加载,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。(二进制字节流按照特定虚拟机的格式存储在方法区之中)
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

二进制字节流并不只是单纯地从Class文件中获取,比如它还可以从Jar包中获取、从网络中获取(最典型的应用便是Applet)、由其他文件生成(JSP应用)等。

5,类的唯一性确认方式:

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性。

即使两个类来源于同一个.class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。

影响到的比较方法有:

1,Class对象的比较:equals()、isAssignableFrom()、isInstance()等方法的返回结果。
2,使用instanceof对对象所属关系的判定结果。

instanceof,isinstance,isAssignableFrom,asSubclass的区别

6,类加载器的双亲委派模型

我们把上层的类加载器叫做当前层类加载器的父加载器,但父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。

该模型在JDK1.2期间被引入并广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者们推荐给开发者的一种类的加载器实现方式。

双亲委派模型的工作流程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,
因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,
只有当父加载器在它的搜索范围中没有找到所需的类时,
即无法完成该加载,子加载器才会尝试自己去加载该类。

7, 使用双亲委派模型来组织类加载器之间的关系的好处

让Java类也具备了一种带有优先级的层次关系:
,防止重复加载。
(例如,类java.lang.Object类存放在JDK\jre\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载)
,可以利于保证类来源的安全性。

8,对于同名的类(包名和类名是一样)为什么不会重复加载。

,当启动虚拟机,引导类加载器会加载jre/lib下的系统类
,扩展类加载器会加载jre/lib/ext下的类
,执行public类的main的方法,应用类加载器尝试去加载。

当加载类时候,是采用双亲加载模式:

,首先应用加载判断自己是否已经加载,如果没有加载,就交给父加载器(扩展类加载器)加载。
补充:应用类加载器并不是继承扩展类加载器,而是结构层次上一级。 ,扩展类加载判断这个是否已经被自己加载,如果没有,查看引导类加载器是否已经加载了。
补充引导类加载是本地方法实现的,只能调用查询的接口,没有调用让其去加载类的接口。 ,如果引导类加载器没有加载,那么扩展类加载器才会尝试去加载。 ,扩展类加载加载的时候判断这个类是否已经在ext/lib的目录下,在的话,尝试加载。如果不在或者加载失败,那么交给应用类加载器。 ,应用类加载器首先判断是否在classpath下,如果类确实存在,应用类加载器才真正去加载该类。

9,如果要加载同名的l类。需要绕开双亲的加载,自己定义类加载器。

,自定义类首先继承类加载器(因为需要用到其中的真正加载类的方法)。
,重写查找类的方法。

10,关于不同加载器加载同一个字节码,被认为是两个不同的类,并且两个类的属性值不可以互相赋值。

补充1

JVM在运行时会对这两个类的加载器进行验证,JVM规范中要求这两个加载器必须要一致,
否则将报类验证错误,即VerifyError的错误,这是为了防止不正常的类冒充正确的类进行类型欺骗。

补充2

一种热替换类的方式,用自定义类加载器加类,将需要的类替换掉(或者替换掉类的内容),需要注意同名类不同加载器赋值问题。
以及之前对该类的调用方式(是否指定了类加载器,或者被记录了加载器和类的关联)。

11,类加载器的源码分析:

java.lang.ClassLoader抽象类中重要的几个方法:

//加载指定名称(包括包名)的二进制类型,供用户调用的接口
public Class<?> loadClass(String name) throws ClassNotFoundException{ … } //加载指定名称(包括包名)的二进制类型,同时指定是否解析(但是这里的resolve参数不一定真正能达到解析的效果),供继承用
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … } //findClass方法一般被loadClass方法调用去加载指定名称类,供继承用
protected Class<?> findClass(String name) throws ClassNotFoundException { … } //定义类型,一般在findClass方法中读取到对应字节码后调用,可以看出不可继承
//(说明:JVM已经实现了对应的具体功能,解析对应的字节码,产生对应的内部数据结构放置到方法区,所以无需覆写,直接调用就可以了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }

ClassLoader详细源码

public abstract class ClassLoader {  

    // 父ClassLoader
private ClassLoader parent; // 被此classLoader加载过的Class对象
private Vector classes = new Vector(); // The packages defined in this class loader. Each package name is mapped
// to its corresponding Package object.
private HashMap packages = new HashMap(); // 由虚拟机调用
void addClass(Class c) {
classes.addElement(c);
} // The packages defined in this class loader. Each package name is mapped
// to its corresponding Package object.
private final HashMap<String, Package> packages = new HashMap<String, Package>(); // 指明parent
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
} // 不指名parent时使用SystemClassLoader
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
} // 默认resolve=false
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1 检查此class是否被此classloader加载过,
// 最终是有native方法返回,native方法会使用到classes集合
Class c = findLoadedClass(name);
// 1.1 未被加载
if (c == null) {
try {
// 1.1.1 此classloader有parent,委托parent去load
if (parent != null) {
c = parent.loadClass(name, false);
} else {// 1.1.2 此classloader无parent = 启动类装载器去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
// 如果没有找到class,自己去加载试试
if (c == null) {
c = findClass(name);
}
}
// 找到的Class对象是否需要连接操作
if (resolve) {
resolveClass(c);
}
// 1.2 被加载过,直接返回
return c;
} protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
} // 如果name里包含/,或者虚拟机不支持class数组你使用了数组的时候返回false,其它情况返回true,包括空的name
// eg com.jyz.component.core.collection.Tuple return true
private boolean checkName(String name) {
if ((name == null) || (name.length() == 0))
return true;
if ((name.indexOf('/') != -1)
|| (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
return false;
return true;
} // 检查package是否可访问
private void checkPackageAccess(Class cls, ProtectionDomain pd) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// ...
}
domains.add(pd);
} // 自定义classloader时重写此方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
} // 将byte数组转换成一个Class对象,最终有native方法实现
// 同一个byte数组,被不同的classloader加载,产生两个不同的Class对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError {
return defineClass(name, b, off, len, null);
} /*
* Determine protection domain, and check that: - not define java.* class, -
* signer of this class matches signers for the rest of the classes in
* package.
*/
//native的defineClass时会调用此方法检查name是否合法
//首先checkName,然后还需要!name.startsWith("java.")
//所以我们定义了java.mypackage包,都将异常
//java.lang.SecurityException: Prohibited package name: java.mypackage
private ProtectionDomain preDefineClass(String name,
ProtectionDomain protectionDomain) {
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException("Prohibited package name: "
+ name.substring(0, name.lastIndexOf('.')));
}
//...
} // protected的resolveClass方法,可以在自定义的classloader调用
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
} //获得appClassLoader,实际调用Launcher完成
public static ClassLoader getSystemClassLoader() {
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
return l.getClassLoader();
} }

ClassLoader类

java.lang.ClassLoader抽象类中默认实现的两个构造函数:

protected ClassLoader() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
//默认将父类加载器设置为系统类加载器,getSystemClassLoader()获取系统类加载器
this.parent = getSystemClassLoader();
initialized = true;
} protected ClassLoader(ClassLoader parent) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
//强制设置父类加载器
this.parent = parent;
initialized = true;
}

java.lang.ClassLoader中的loadClass(String name)方法的代码就可以分析出虚拟机默认采用的双亲委派机制到底是什么模样:

public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException { // 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,
//通过调用本地方法native findBootstrapClass0(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}

系统类加载器和扩展类加载器是在Launcher类的内部类:

public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher(); public static Launcher getLauncher() {
return launcher;
} private ClassLoader loader; //ClassLoader.getSystemClassLoader会调用此方法
public ClassLoader getClassLoader() {
return loader;
} public Launcher() {
// 1. 创建ExtClassLoader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
} // 2. 用ExtClassLoader作为parent去创建AppClassLoader
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
} // 3. 设置AppClassLoader为ContextClassLoader
Thread.currentThread().setContextClassLoader(loader);
//...
} static class ExtClassLoader extends URLClassLoader {
private File[] dirs; public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
return new ExtClassLoader(dirs);
} public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
this.dirs = dirs;
} private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
//...
return dirs;
}
} /**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[] : getClassPath(s); URL[] urls = (s == null) ? new URL[] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
} AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
} /**
* Override loadClass so we can checkPackageAccess.
* 这个方法似乎没什么必要,因为super.loadClass(name, resolve)时也会checkPackageAccess
*/
public synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//
sm.checkPackageAccess(name.substring(, i));
}
}
return (super.loadClass(name, resolve));
} }
}

ClassLoader.loadClass()的最后一步是调用findClass(),这个方法在ClassLoader中并未实现,由其子类负责实现。

findClass()的功能是找到class文件并把字节码加载到内存中。(自定义的ClassLoader一般覆盖这个方法。——以便使用不同的加载路径。)

URLClassLoader.findClass()如下:

 /* The search path for classes and resources */
URLClassPath ucp;
/* The context to be used when loading classes and resources */
private AccessControlContext acc; /**
* Finds and loads the class with the specified name from the URL search
* path. Any URLs referring to JAR files are loaded and opened as needed
* until the class is found.
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return (Class)
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
// 1. URLClassPath ucp,帮助获取class文件字节流
// URLClassPath会用FileLoader或者JarLoader去加载字节码
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 2. defineClass,创建类对象,将字节流解析成JVM能够识别的Class对象。
return defineClass(name, res, true);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}

ClassLoader.resolveClass()源码如下:

加载完字节码后,会根据需要进行验证、解析。

protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class c);

由此可以知道:

,双亲加载模式的具体实现
,安全检测的代码开始执行时机(security.checkCreateClassLoader())(补充:SecurityManager入门
,验证和解析的代码开始时机(resolveClass(c))

根据继承关系:

和方法实现:

可以看出:只要继承任意子类都还是双亲加载模式,除非重写loadClass().

标准扩展类加载器和系统类加载器及其父类(java.NET.URLClassLoader和java.security.SecureClassLoader)
都没有覆写java.lang.ClassLoader中默认的加载委派规则---loadClass(…)方法。

12,java程序动态扩展方式

允许用户运行时扩展引用程序:
1,既可以通过当前虚拟机中预定义的加载器加载编译时已知的类或者接口,
2,又允许用户自行定义类装载器,在运行时动态扩展用户的程序。
你的程序可以装载在编译时并不知道或者尚未存在的类或者接口,并动态连接它们并进行有选择的解析。

运行时动态扩展java应用程序有如下两个途径:

1 调用java.lang.Class.forName(…)加载类

这里主要说明的是多参数版本的forName(…)方法:

public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException 

这里的initialize参数是很重要的。

它表示在加载同时是否完成初始化的工作(说明:单参数版本的forName方法默认是完成初始化的)。

有些场景下需要将initialize设置为true来强制加载同时完成初始化。

例如典型的就是利用DriverManager进行JDBC驱动程序类注册的问题。

因为每一个JDBC驱动程序类的静态初始化方法都用DriverManager注册驱动程序,这样才能被应用程序使用。
这就要求驱动程序类必须被初始化,而不单单被加载。Class.forName的一个很常见的用法就是在加载数据库驱动的时候。 如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用来加载 Apache Derby 数据库的驱动。

如果loader的参数设置为null那么会用启动类加载。

常用无参方法默认的加载器是应用类加载器,如下情况

Class.forName("Foo")
is equivalent to:
Class.forName("Foo", true, this.getClass().getClassLoader())

2 用户自定义类加载器

findClass()定义加载路径:

findClass()的功能是找到class文件并把字节码加载到内存中。
自定义的ClassLoader一般覆盖这个方法。(以便使用不同的加载路径。)
在其中调用defineClass()解析字节码。

loadClass()定义加载机制:

1,自定义的加载器可以覆盖该方法loadClass(),以便定义不同的加载机制。
例如:
Servlet中的WebappClassLoader覆盖了该方法,在WEB-INFO/classes目录下查找类文件;在加载时,如果成功,则缓存到ResourceEntry对象。(不同的加载机制)。
2,AppClassLoader覆盖了loadClass()方法。
如果自定义的加载器仅覆盖了findClass,而未覆盖loadClass(即加载规则一样,但加载路径不同);
则调用getClass().getClassLoader()返回的仍然是AppClassLoader!因为真正load类的,还是AppClassLoader。

实现类的热部署:

JVM默认不能热部署类,因为加载类时会去调用findLoadedClass(),如果类已被加载,就不会再次加载。

JVM判断类是否被加载有两个条件:完整类名是否一样、ClassLoader是否是同一个。

所以要实现热部署的话,只需要使用ClassLoader的不同实例来加载。

MyClassLoader cl1 = new MyClassLoader();
Class c1 = cl1.findClass("Test.class");
c1.newInstance(); MyClassLoader cl2 = new MyClassLoader();
Class c2 = cl2.findClass("Test.class");
c2.newInstance();

上例中的c1和c2就是两个不同的实例。

如果用同一个ClassLoader实例来加载,则会抛LinkageError。

13,区别不同类加载器:

真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。
1,真正完成类的加载工作是通过调用defineClass来实现的(称为一个类的定义加载器(defining loader);
2,而启动类的加载过程是通过调用loadClass来实现的。称为初始加载器(initiating loader)。

在Java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。

也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。

两种类加载器的关联之处在于:

一个类的定义加载器是它引用的其它类的初始加载器。

如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。

14,文件系统类加载器

package classloader;  

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; // 文件系统类加载器
public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
} // 获取类的字节码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name); // 获取类的字节数组
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
} private byte[] getClassData(String className) {
// 读取类文件的字节
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取类文件的字节码
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
} private String classNameToPath(String className) {
// 得到类文件的完全路径
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
} }

为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。

package com.example;
public class Sample {
private Sample instance;
public void setSample(Object instance) {
System.out.println(instance.toString());
this.instance = (Sample) instance;
}
}
package classloader;  

import java.lang.reflect.Method;  

public class ClassIdentity {  

    public static void main(String[] args) {
new ClassIdentity().testClassIdentity();
} public void testClassIdentity() {
String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = "com.example.Sample";
try {
Class<?> class1 = fscl1.loadClass(className); // 加载Sample类
Object obj1 = class1.newInstance(); // 创建对象
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
} } }

运行输出:com.example.Sample@7852e922

15,网络类加载器

通过类加载器来实现组件的动态更新。

场景是:

Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。
当有版本更新的时候,只需要替换掉服务器上保存的文件即可。

类 NetworkClassLoader负责通过网络下载Java类字节代码并定义出Java类。

package classloader;  

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL; public class NetworkClassLoader extends ClassLoader { private String rootUrl; public NetworkClassLoader(String rootUrl) {
// 指定URL
this.rootUrl = rootUrl;
} // 获取类的字节码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
} private byte[] getClassData(String className) {
// 从网络上读取的类的字节
String path = classNameToPath(className);
try {
URL url = new URL(path);
InputStream ins = url.openStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取类文件的字节
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
} private String classNameToPath(String className) {
// 得到类文件的URL
return rootUrl + "/"
+ className.replace('.', '/') + ".class";
}
}

在通过NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。

第一种做法是使用Java反射API。
另外一种做法是使用接口。

需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。

使用Java反射API可以直接调用Java类的方法。

而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。

在客户端通过相同的接口来使用这些实现类。我们使用接口的方式。示例如下:

//客户端接口
package classloader;
public interface Versioned { String getVersion();
}
package classloader;  

public interface ICalculator extends Versioned {  

    String calculate(String expression);
}

网络上的不同版本的类:

package com.example;  

import classloader.ICalculator;  

public class CalculatorBasic implements ICalculator {  

    @Override
public String calculate(String expression) {
return expression;
} @Override
public String getVersion() {
return "1.0";
} }
package com.example;  

import classloader.ICalculator;  

public class CalculatorAdvanced implements ICalculator {  

    @Override
public String calculate(String expression) {
return "Result is " + expression;
} @Override
public String getVersion() {
return "2.0";
} }

在客户端加载网络上的类的过程:

package classloader;  

public class CalculatorTest {  

    public static void main(String[] args) {
String url = "http://localhost:8080/ClassloaderTest/classes";
NetworkClassLoader ncl = new NetworkClassLoader(url);
String basicClassName = "com.example.CalculatorBasic";
String advancedClassName = "com.example.CalculatorAdvanced";
try {
Class<?> clazz = ncl.loadClass(basicClassName); // 加载一个版本的类
ICalculator calculator = (ICalculator) clazz.newInstance(); // 创建对象
System.out.println(calculator.getVersion());
clazz = ncl.loadClass(advancedClassName); // 加载另一个版本的类
calculator = (ICalculator) clazz.newInstance();
System.out.println(calculator.getVersion());
} catch (Exception e) {
e.printStackTrace();
} } }

16,类加载器加载路径

java中获取类加载路径和项目根路径的5种方法

Java类加载器(classloader)及类加载路径简介

类加载器获取资源路径

总结:加载过程中,主要加载器方法的执行步骤。

//其实就是不同实例的方法
1,loadClass
2,findClass
3,difineClass
3,resovleClass

来源:

深入理解Java类加载器(1):Java类加载原理解析

http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

http://zy19982004.iteye.com/blog/1983236

http://zy19982004.iteye.com/blog/1983240

http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

jvm(1)类的加载(二)(自定义类加载器)的更多相关文章

  1. jvm(1)类的加载(三)(线程上下文加载器)

    简介: 类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的. Java Applet 需要从远程下载 Java 类文件到浏览器中并执行. 现在类加载器在 ...

  2. JVM 1.类的加载、连接、初始化

    Java类的加载是由类加载器来完成的,过程如下: 首先,加载是把硬盘.网络.数据库等的class文件中的二进制数据加载到内存的过程,然后会在Java虚拟机的运行时数据区的堆区创建一个Class对象,用 ...

  3. JVM:java类的加载机制

    原文连接:https://www.cnblogs.com/ityouknow/p/5603287.html 类加载机制的奥妙. 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读 ...

  4. VS中调试查看DataTable和DataSet时未能加载此自定义查看器解决方法

    在网上找了几个方法,感觉不太实用,最后自己找到了问题所在  VS2017中选择调试-选项-常规中的使用托管兼容模式取消勾选.之后就可以了

  5. jvm系列 (五) ---类的加载机制

    类的加载机制 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 jvm系列 (四) ---强.软.弱.虚引用 我的博客目录 什么是类 ...

  6. java 类的加载,链接,初始化

    本篇的话题,讨论Java类的加载.链接和初始化.Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码 ...

  7. Java类的加载 链接 初始化

    原文地址 Java类的加载.链接和初始化.Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象.一个Java类从字节代码到能够 ...

  8. Java类的加载、链接和初始化

    一.Java的类加载机制回顾与总结: 我们知道一个Java类要想运行,必须由jvm将其装载到内存中才能运行,装载的目的就是把Java字节代码转换成JVM中的java.lang.Class类的对象.这样 ...

  9. jvm学习二:类加载器

    前一节详细的聊了一下类的加载过程,本节聊一聊类的加载工具,类加载器  ---  ClassLoader 本想自己写的,查资料的时候查到一篇大神的文章,写的十分详细 大家直接过去看吧http://blo ...

随机推荐

  1. vc到vs2015消息函数

    afx_msg LRESULT OnMyIconNotify(WPARAM wParam,LPARAM lParam); vc6 可以是void  vs2015不可以 ON_MESSAGE(MYWM_ ...

  2. 关于document.write(来自网络)

    对象属性: document.title                 //设置文档标题等价于HTML的<title>标签document.bgColor               / ...

  3. 【Log】logback的配置和使用(一)

    logback介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和logback-access ...

  4. 为什么c++中返回成员变量的指针,会破坏了封装?

    上述代码中,get()函数返回的是类成员变量的name的地址,这是很危险的,name是私有的,意味这不想被客户访问,但是如果返回name的地址,那么外部函数就可以修改name,这就破坏了封装性. 为什 ...

  5. Devexpress VCL Build v2014 vol 14.2.4 发布

    What's New in 14.2.4 (VCL Product Line)   New Major Features in 14.2 What's New in VCL Products 14.2 ...

  6. 2018.07.22 洛谷P2986 伟大的奶牛聚集(树形dp)

    传送门 给出一棵树,树有边权和点权,若选定一个点作为中心,这棵树的代价是所有点权乘上到根的距离的和.求代价最小. 解法:一道明显的换根dp" role="presentation& ...

  7. (最短路 dijkstra)昂贵的聘礼 -- poj -- 1062

    链接: http://poj.org/problem?id=1062 昂贵的聘礼 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions ...

  8. (匹配)The Accomodation of Students --HDU --2444

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=2444 http://acm.hust.edu.cn/vjudge/contest/view.action ...

  9. Bezier曲线

    1. 学习网址 http://give.zju.edu.cn/cgcourse/new/book/8.2.htm

  10. 深入浅析Node.js单线程模型

    Node.js采用 事件驱动 和 异步I/O 的方式,实现了一个单线程.高并发的运行时环境,而单线程就意味着同一时间只能做一件事,那么Node.js如何利用单线程来实现高并发和异步I/O?本文将围绕这 ...