命名空间介绍
每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成。也就是说,命名空间就是一个空间,空间里就是可以所有可以加载到的类。
命名空间的性质如下:
①在同一个命名空间中,不会出现类的二进制名字相同的两个类。
②在不同的命名空间中,有可能会出现类的二进制名字相同的两个类。
实例如下:
验证性质一
public class MyTest extends ClassLoader {
private String classLoaderName;
private String fileExtension = ".class";
private String path;
public MyTest(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public MyTest(ClassLoader parent, String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass invoke: "+name);
System.out.println("class loader name: "+classLoaderName);
byte[] classData = getClassData(name);
return defineClass(null, classData, 0, classData.length);
}
public byte[] getClassData(String className) {
byte[] bytes = null;
InputStream is = null;
ByteArrayOutputStream bos = null;
className=className.replaceAll("\\.","\\\\");
try {
File file=new File(this.path,className+fileExtension);
is = new FileInputStream(file);
bos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = is.read()) != -1) {
bos.write(ch);
}
bytes = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyTest myTest=new MyTest("MyLoader");
myTest.setPath("桌面路径");
//使用加载器myTest加载的类
Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
Object instance = loadClass.newInstance();
System.out.println(loadClass.hashCode);
System.out.println(instance);
System.out.println("------");
//将myTest1加载器的父加载器设置为myTest
MyTest myTest1=new MyTest(myTest,"MyLoader1");
myTest1.setPath("桌面路径");
//使用加载器myTest1加载的类
Class<?> loadClass1 = myTest1.loadClass("com.minato.jvm.chapter15.People");
Object instance1 = loadClass.newInstance();
System.out.println(loadClass1.hashCode);
System.out.println(instance1);
}
}
打印结果
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader //调用了myTest的findClass方法
21685669 //myTest加载器加载出的类对象的hashCode
com.minato.jvm.chapter15.People@7f31245a
------
21685669 //myTest1加载器加载出的类对象的hashCode
com.minato.jvm.chapter15.People@6d6f6e28
两个hashCode值相同,并且findClass方法只执行了一次,证明了加载流程执行了一次。
类加载器的执行顺序
调用findLoadedClass方法检查欲加载的类是否已经加载
调用父加载器的loadClass,如果父加载器为空,那么就会用系统类加载加载
调用findClass去find类
按照双亲委托模型,加载器myTest1当执行findLoadedClass时,会返回true,
后续的顺序并不会执行,因此执行流程只执行一次,返回的值是已经加载的Class,
因此两次hashCode值相同。
验证性质二
public class MyTest extends ClassLoader {
private String classLoaderName;
private String fileExtension = ".class";
private String path;
public MyTest(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public MyTest(ClassLoader parent, String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass invoke: "+name);
System.out.println("class loader name: "+classLoaderName);
byte[] classData = getClassData(name);
return defineClass(null, classData, 0, classData.length);
}
public byte[] getClassData(String className) {
byte[] bytes = null;
InputStream is = null;
ByteArrayOutputStream bos = null;
className=className.replaceAll("\\.","\\\\");
try {
File file=new File(this.path,className+fileExtension);
is = new FileInputStream(file);
bos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = is.read()) != -1) {
bos.write(ch);
}
bytes = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyTest myTest=new MyTest("MyLoader");
myTest.setPath("桌面路径");
//使用加载器myTest加载的类
Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
Object instance = loadClass.newInstance();
System.out.println(loadClass.hashCode);
System.out.println(instance);
System.out.println("------");
MyTest myTest1=new MyTest("MyLoader1");
myTest1.setPath("桌面路径");
//使用加载器myTest1加载的类
Class<?> loadClass1 = myTest1.loadClass("com.minato.jvm.chapter15.People");
Object instance1 = loadClass.newInstance();
System.out.println(loadClass1.hashCode);
System.out.println(instance1);
}
}
打印结果
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader // 调用了加载器myTest的findClass方法
21685669 //加载出的类的hashCode
com.minato.jvm.chapter15.People@7f31245a //创建出的实例
------
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader1 // 调用了加载器myTest1的findClass方法
1173230247 //加载出的类对象的hashCode
com.minato.jvm.chapter15.People@330bedb4
以上的加载器myTest和myTest1是两个不同的类加载器,也就是说有两个不用命名空间,
因此完整的走了两次加载流程,hashCode的值不同
类的卸载
由JVM自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。JVM自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。JVM本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类Class对象,因此这些Class对象始终是可触及的。
以上的关键是 JVM本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类Class对象
由用户自定义的类加载器所加载的类是可以被卸载的,因为JVM不会始终保持自定义类加载器的引用。
实例
配置JVM参数 -XX:+TraceClassUnloading 追踪类的卸载
方法是 将引用断开
public class MyTest extends ClassLoader {
private String classLoaderName;
private String fileExtension = ".class";
private String path;
public MyTest(String classLoaderName) {
super();
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public MyTest(ClassLoader parent, String classLoaderName) {
super(parent);
this.classLoaderName = classLoaderName;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass invoke: " + name);
System.out.println("class loader name: " + classLoaderName);
byte[] classData = getClassData(name);
return defineClass(null, classData, 0, classData.length);
}
public byte[] getClassData(String className) {
byte[] bytes = null;
InputStream is = null;
ByteArrayOutputStream bos = null;
className = className.replaceAll("\\.", "\\\\");
try {
File file = new File(this.path, className + fileExtension);
is = new FileInputStream(file);
bos = new ByteArrayOutputStream();
int ch = 0;
while ((ch = is.read()) != -1) {
bos.write(ch);
}
bytes = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
bos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bytes;
}
public static void test(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> loadClass = classLoader.loadClass("com.minato.jvm.chapter15.People");
Object instance = loadClass.newInstance();
System.out.println(instance.getClass().getClassLoader());
System.out.println(instance);
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyTest myTest = new MyTest("MyLoader");
myTest.setPath("桌面路径");
//使用加载器myTest加载的类
Class<?> loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
Object instance = loadClass.newInstance();
System.out.println("------");
//引用断开
myTest = null;
loadClass = null;
instance = null;
//回收 生命周期终止
System.gc();
myTest = new MyTest("MyLoader");
myTest.setPath("桌面路径");
//使用加载器myTest加载的类
loadClass = myTest.loadClass("com.minato.jvm.chapter15.People");
instance = loadClass.newInstance();
System.out.println("------");
}
}
结果如下:
findClass invoke: com.minato.jvm.chapter15.People
class loader name: MyLoader
------
[Unloading class com.minato.jvm.chapter15.People //类被卸载 0x0000000100061828]
findClass invoke: com.minato.jvm.chapter15.People //累被重新加载
class loader name: MyLoader
------