类加载2
jvm参数
-XX:+TraceClassLoading:用来查看类的加载信息
-XX:+TraceClassUnloading:用来查看类的卸载信息
例子1
class Parent {
static int a = 3;
static {
System.out.println("Parent static block");
}
static void doSomething() {
System.out.println("do something");
}
}
class Child extends Parent {
static {
System.out.println("Child static block");
}
}
public class MyTest {
public static void main(String[] args) {
System.out.println(Child.a);
System.out.println("+++");
Child.doSomething();
}
}
输出
Parent static block
3
+++
do something
分析:
定义在哪,就是对谁的主动使用
例子2
package com.test;
import java.util.Random;
import java.util.UUID;
class CL {
static {
System.out.println("Class Cl");
}
}
public class MyTest {
public static void main(String[] args) throws Exception {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass("com.test.CL");
System.out.println(clazz);
System.out.println("+++++");
clazz = Class.forName("com.test.CL");
System.out.println(clazz);
}
}
输出
class com.test.CL
+++++
Class Cl
class com.test.CL
分析:
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。而Class.forName("com.test.CL")的反射才会对类进行初始化
1、类加载器(ClassLoader)

对于数组来说,JavaDoc经常将构成数组的元素称为Component,实际上就是将数组降低一个维度后的类型
例子
package com.test;
import java.util.Random;
import java.util.UUID;
public class MyTest {
public static void main(String[] args) throws Exception {
String[] s = new String[2];
System.out.println(s.getClass().getClassLoader());
System.out.println("+++++");
MyTest[] myTest = new MyTest[2];
System.out.println(myTest.getClass().getClassLoader());
System.out.println("+++++");
int[] a = new int[2];
System.out.println(a.getClass().getClassLoader());
}
}
输出
null
+++++
sun.misc.Launcher$AppClassLoader@18b4aac2
+++++
null
分析:
数组类的类加载器与其元素类型的类加载器相同,如果该元素类型是基本类型,则该数组类没有加载器
1、String[] 返回String类型的加载器 null(这里指根加载器)
2、MyTest[] 返回MyTest类型的加载器 AppClassLoader(系统加载器)
3、int[] 是基本类型,因此返回null(这里指没有加载器)
2、自定义类加载器
package com.test;
import java.io.*;
public class MyTest extends ClassLoader {
private String classLoaderName;
private final String fileExtension = ".class";
public MyTest(String classLoaderName)
{
super();// 将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest(ClassLoader parent, String classLoaderName)
{
super(parent);// 显示指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String classname) throws ClassNotFoundException {
System.out.println("findClass invoked:" + classname);
System.out.println("class loader name: " + this.classLoaderName);
byte[] data = this.loadClassData(classname);
return this.defineClass(classname, data, 0, data.length);
}
private byte[] loadClassData(String className)
{
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "/");
try {
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while(((ch = is.read()) != -1))
{
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception{
MyTest loader1 = new MyTest("loader1");
Class<?> clazz = classLoader.loadClass("com.test.MyTest1");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
}
}
从磁盘上指定好的一个class文件,读取到内存当中,放入到一个字节数组里面,class的信息就位于内存当中,再通过defineClass方法,将字节数组转换成一个class对象。
1、自定义加载器必须实现继承ClassLoader类
2、loadClassData方法,根据这个名字,把对应的文件找到,以输入输出流的形式获取类文件的字节码数组
3、definclass()方法,是将你定义的字节码文件经过字节数组流解密之后,将该字节流数组生成字节码文件,也就是该类的 文件的类名.class,产生对应的内部数据结构放置到方法区(一般在findClass方法中读取到对应字节码后调用,final的,不能被继承 )
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
4、findClass() 方法
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>.
文档翻译:
查找具有指定的名字binary name 。 该方法应该被加载类的委托模型后面的类加载器实现覆盖,并且在检查所请求的类的父类加载器之后将被loadClass
方法调用。 默认实现会抛出一个ClassNotFoundException
findClass方法是通过loadClass方法调用使用的
@Override
protected Class<?> findClass(String classname) throws ClassNotFoundException {
byte[] data = this.loadClassData(classname);
return this.defineClass(classname, data, 0, data.length);
}
5、loadClass()方法
源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先判断该类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
//如果存在父类加载器,就委派给父类加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else { // 递归终止条件
// 由于启动类加载器无法被Java程序直接引用,因此默认用 null 替代
// parent == null就意味着由启动类加载器尝试加载该类,
// 即通过调用 native方法 findBootstrapClass0(String name)加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
// 如果父类加载器不能完成加载请求时,再调用自身的findClass方法进行类加载,若加载成功,findClass方法返回的是defineClass方法的返回值
// 注意,若自身也加载不了,会产生ClassNotFoundException异常并向上抛出
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;
}
}
3、分析最初的自定义类加载器代码(代码在上面)
输出是:
com.test.MyTest1@1540e19d
sun.misc.Launcher$AppClassLoader@18b4aac2
很明显,findClass根本没有执行过,在说明几个函数的分析,可以知道对于加载MyTest1类加载成功的类加载器是系统类加载器,已经由上一层的类加载器解决了,因此自己定义的类加载器不需要进行加载,所有没有使用过自己定义的findClass
有如下操作
在项目中有com.Test.MyTest1的文件,在桌面中也创建一个com.Test.MyTest1的文件,使用一个自定义的类加载器去加载桌面上的MyTest1文件,分两种情况
1、若在项目中存在MyTest1.class 文件,则父加载器就可以解决,类加载器是系统类加载器
2、若在项目中不存在MyTest1.class文件,则会调用自己创建的类加载器的findClass方法,类加载器是MyTest(父加载器找不到这个MyTest1.class,并且自己不能进行加载,因此只能抛出异常给儿子自己加载)
具体代码
package com.test;
import java.io.*;
public class MyTest extends ClassLoader {
private String classLoaderName;//加载器名字
private String path;
private final String fileExtension = ".class";
public MyTest(String classLoaderName)
{
super();// 将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest(ClassLoader parent, String classLoaderName)
{
super(parent);// 显示指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public void setPath(String path)
{
this.path = path;
}
@Override
protected Class<?> findClass(String classname) throws ClassNotFoundException {
System.out.println("findClass invoked:" + classname);
System.out.println("class loader name: " + this.classLoaderName);
byte[] data = this.loadClassData(classname);
return this.defineClass(classname, data, 0, data.length);
}
private byte[] loadClassData(String className)
{
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "/");
try {
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while(((ch = is.read()) != -1))
{
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception{
MyTest loader1 = new MyTest("loader1");
//loader1.setPath("F:/titled2/src/main/java/");
//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
loader1.setPath("C:/Users/小呆呆/Desktop/");
Class<?> clazz = loader1.loadClass("com.test.MyTest1");
System.out.println("class: " + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object);
}
}
输出
findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a
命名空间(和类加载3 命名空间的关系结合一起看)
- 每个类加载器都有自己的命名空间,命名空间由该加载器的类及所有父加载器所加载的类组成
- 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
- 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
package com.test;
import java.io.*;
public class MyTest extends ClassLoader {
private String classLoaderName;//加载器名字
private String path;
private final String fileExtension = ".class";
public MyTest(String classLoaderName)
{
super();// 将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest(ClassLoader parent, String classLoaderName)
{
super(parent);// 显示指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public void setPath(String path)
{
this.path = path;
}
@Override
protected Class<?> findClass(String classname) throws ClassNotFoundException {
System.out.println("findClass invoked:" + classname);
System.out.println("class loader name: " + this.classLoaderName);
byte[] data = this.loadClassData(classname);
return this.defineClass(classname, data, 0, data.length);
}
private byte[] loadClassData(String className)
{
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "/");
try {
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while(((ch = is.read()) != -1))
{
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception{
MyTest loader1 = new MyTest("loader1");
//loader1.setPath("F:/titled2/src/main/java/");
loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
Class<?> clazz = loader1.loadClass("com.test.MyTest1");
System.out.println("class: " + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object);
System.out.println();
MyTest loader2 = new MyTest("loader2");
//loader1.setPath("F:/titled2/src/main/java/");
loader2.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
Class<?> clazz2 = loader2.loadClass("com.test.MyTest1");
System.out.println("class: " + clazz2.hashCode());
Object object2 = clazz2.newInstance();
System.out.println(object2);
}
在项目中有com.Test.MyTest1的文件,在桌面中也创建一个com.Test.MyTest1的文件,使用两个自定义的类加载器去加载桌面上的MyTest1文件,分两种情况
同一个命名空间中,加载同一个.class文件产生出来的.class对象是相同的
1、若在项目中存在MyTest1.class文件,都是由系统类加载器直接去加载,不会调用自己创建的类加载器的findClass方法,加载的类的类名相同(因此clazz == clazz2 返回true)
输出
class: 356573597
com.test.MyTest1@677327b6
class: 356573597
com.test.MyTest1@677327b6
不同的命名空间中,加载同一个.class文件产生出来的.class对象是不同的,两个.class对象各自放在对应的命名空间中
2、若在项目中不存在MyTest1.class文件,则两个自定义加载器都会调用自己创建的类加载器的findClass方法,类加载器是自己本身,同时在同一个命名空间中,不会出现类相同的完整名字的相同两个类,因此两个类的类名不同(hascode不同)(loader1和loader2没有任何关系,在内存当中形成了两个命名空间,在各自的命名空间加载出对应的类,加载出来的两个.class对象是不同的,因此clazz == clazz2 返回false)
输出
findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a
findClass invoked:com.test.MyTest1
class loader name: loader2
class: 1173230247
com.test.MyTest1@330bedb4
类的卸载
- 当MySample类被加载、连接和初始化后,它的生命周期就开始了。当代表MySample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,MySample类在方法区的数据也会被卸载,从而结束Sample类的生命周期
- 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期
- 由Java虚拟机自带的类记载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。前面介绍过,Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的
- 由用户自定义的类加载器所加载的类是可以被卸载的
例子
将项目中的MyTest1删除,通过自定义类记载器加载桌面上的MyTest1,加载完后,将loader1指向其他地方,并进行垃圾回收
package com.test;
import java.io.*;
public class MyTest extends ClassLoader {
private String classLoaderName;//加载器名字
private String path;
private final String fileExtension = ".class";
public MyTest(String classLoaderName)
{
super();// 将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest(ClassLoader parent, String classLoaderName)
{
super(parent);// 显示指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public void setPath(String path)
{
this.path = path;
}
@Override
protected Class<?> findClass(String classname) throws ClassNotFoundException {
System.out.println("findClass invoked:" + classname);
System.out.println("class loader name: " + this.classLoaderName);
byte[] data = this.loadClassData(classname);
return this.defineClass(classname, data, 0, data.length);
}
private byte[] loadClassData(String className)
{
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".", "/");
try {
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while(((ch = is.read()) != -1))
{
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception ex){
ex.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception{
MyTest loader1 = new MyTest("loader1");
//loader1.setPath("F:/titled2/src/main/java/");
loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
Class<?> clazz = loader1.loadClass("com.test.MyTest1");
System.out.println("class: " + clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object);
System.out.println();
loader1 = null;
clazz = null;
object = null;
System.gc();//调用垃圾回收
loader1 = new MyTest("loader1");
//loader1.setPath("F:/titled2/src/main/java/");
loader1.setPath("C:/Users/小呆呆/Desktop/");//当父类加载器都解决不了的时候,才会调用自身的findClass往这个地址找
clazz = loader1.loadClass("com.test.MyTest1");
System.out.println("class: " + clazz.hashCode());
Object object2 = clazz.newInstance();
System.out.println(object2);
}
}
输出(加入 -XX:+TraceClassUnloading:用来查看类的卸载信息)
findClass invoked:com.test.MyTest1
class loader name: loader1
class: 21685669
com.test.MyTest1@7f31245a
[Unloading class com.test.MyTest1 0x0000000100061028]
findClass invoked:com.test.MyTest1
class loader name: loader1
class: 1173230247
com.test.MyTest1@330bedb4