JAVA(2)- JVM类加载器
前言
JVM类加载器扩展:
概念
- JVM: Java虚拟机。
1、JVM内置三大类加载器
1.1根类加载器介绍
- Bootstarp类加载器:C++编写,负责虚拟机核心类库的加载,有加载整个java.lang包
- 可通过-Xbootclasspth来指定根加载器的路径
//根加载器获取加载路径
System.getProperty("sun.boot.class.path")
public class BootstrapClassLoader {
public static void main(String[] args) {
System.out.println("Bootstrap:"+String.class.getClassLoader());
String[] paths = System.getProperty("sun.boot.class.path").split(";");
for (String path : paths) {
System.out.println(path);
}
}
}
Bootstrap:null
D:\Java\jdk1.8.0_171\jre\lib\resources.jar
D:\Java\jdk1.8.0_171\jre\lib\rt.jar
D:\Java\jdk1.8.0_171\jre\lib\sunrsasign.jar
D:\Java\jdk1.8.0_171\jre\lib\jsse.jar
D:\Java\jdk1.8.0_171\jre\lib\jce.jar
D:\Java\jdk1.8.0_171\jre\lib\charsets.jar
D:\Java\jdk1.8.0_171\jre\lib\jfr.jar
D:\Java\jdk1.8.0_171\jre\classes
1.2扩展类夹杂器
- ExtClassLoader:主要用于加载JAVA_HOME下的jre\lib\ext子目录里面的类库,由纯java语言实现。java.lang.URLClassLoader的子类,完整类名是sun.misc.Launcher$ExtClassLoader。
//扩展类加载器获取加载路径
System.getProperty("java.ext.dirs")
public class ExtClassLoader {
public static void main(String[] args) {
String[] paths = System.getProperty("java.ext.dirs").split(";");
for (String path : paths) {
System.out.println(path);
}
}
}
D:\Java\jdk1.8.0_171\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
1.3系统类加载器
- ApplicationClassLoader:加载classpath下的类库资源,是自定义类加载器的默认父加载器,系统类加载器路径一般通过
-calsspath
或者-cp
指定
//系统类加载器获取加载路径
System.getProperty("java.class.path")
public class ApplicationClassLoader {
public static void main(String[] args) {
System.out.println(ApplicationClassLoader.class.getClassLoader());
String[] paths = System.getProperty("java.class.path").split(";");
for (String path : paths) {
System.out.println(path);
}
}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
D:\Java\jdk1.8.0_171\jre\lib\charsets.jar
D:\Java\jdk1.8.0_171\jre\lib\deploy.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\access-bridge-64.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\cldrdata.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\dnsns.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\jaccess.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\jfxrt.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\localedata.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\nashorn.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunec.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunjce_provider.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunmscapi.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\sunpkcs11.jar
D:\Java\jdk1.8.0_171\jre\lib\ext\zipfs.jar
D:\Java\jdk1.8.0_171\jre\lib\javaws.jar
D:\Java\jdk1.8.0_171\jre\lib\jce.jar
D:\Java\jdk1.8.0_171\jre\lib\jfr.jar
D:\Java\jdk1.8.0_171\jre\lib\jfxswt.jar
D:\Java\jdk1.8.0_171\jre\lib\jsse.jar
D:\Java\jdk1.8.0_171\jre\lib\management-agent.jar
D:\Java\jdk1.8.0_171\jre\lib\plugin.jar
D:\Java\jdk1.8.0_171\jre\lib\resources.jar
D:\Java\jdk1.8.0_171\jre\lib\rt.jar
E:\java\github-repository\roc-go\roc-go-understand\target\classes
E:\java\github-repository\roc-go\roc-go-common\target\classes
……
……
……
2、自定义类加载器
-
由于双亲委托机制,所以要将HelloWorld.java和HelloWorld.class在当前项目目录下删除
-
HelloWorld.java
public class HelloWorld {
static {
System.out.println(" HelloWorld class static code block");
}
public String hello(){
System.out.println(" HelloWorld hello method");
return "hello";
}
}
- 自定义类加载器,加载HelloWorld
//自定义加载器必须是ClassLoader的直接或间接子类
public class MyClassLoader extends ClassLoader {
//定义默认的class存放路径
public static final Path DEFAULT_CLASS_DIR = Paths.get("E:\\");
//允许指定class存放路径
private final Path classDir;
//默认加载目录
public MyClassLoader() {
super();
this.classDir = DEFAULT_CLASS_DIR;
}
public MyClassLoader(String classDir) {
super();
this.classDir = Paths.get(classDir);
}
public MyClassLoader(ClassLoader parent, String classDir) {
super(parent);
this.classDir = Paths.get(classDir);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取类的二进制流
byte[] classBytes = this.readClassBytes(name);
if (Objects.isNull(classBytes) || classBytes.length == 0) {
throw new ClassNotFoundException("can not load the class " + name);
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] readClassBytes(String name) throws ClassNotFoundException {
//将包名转换为文件路径分隔符
String classPath = name.replace(".", "/");
//拼接上classDir路径 class文件全路径
Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
if (!classFullPath.toFile().exists()) {
throw new ClassNotFoundException("The class " + name + " not found .");
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Files.copy(classFullPath, baos);
return baos.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("load the class " + name + " not occur error .", e);
}
}
@Override
public String toString() {
return "My ClassLoader";
}
}
public class TestClassLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader classLoader = new MyClassLoader();
Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
System.out.println(aClass.getClassLoader());
Object instance = aClass.newInstance();
System.out.println(instance);
Method hello = aClass.getMethod("hello");
Object invoke = hello.invoke(instance);
System.out.println("Result:"+invoke);
}
}
输出结果:
My ClassLoader
HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@378fd1ac
HelloWorld hello method
Result:hello
3、双亲委托机制
概念
-
当一个类加载器被调用loadClass之后,它并不会直接加载,而是去找父加载器直到最顶层根加载器,开始自上而下进行加载。
-
ClassLoader#loadClass源码片段
//resolve ; 是否进行解析
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 {
//parent为空 从根加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果父加载器加载之后还是 null
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;
}
}
-
loadClass流程
- 从当前已加载类中通过全路径名来获取该类,如果有则直接返回。
- 未被加载,开始判断是否有父加载器有父加载器委托给父加载器加载,否则委托给根加载器加载。
- 如果父加载器还是无法加载class、则调用当前类加载器方法
findClass(name)
加载 - 如果类被成功加载,做一些性能数据统计
- 由于loadClass制定了resolve为false,所以不会进行连接阶段的执行,这就解释了为什么通过类加载器不会导致类的初初始化
-
如何不删除HelloWorld.java和HelloWorld.class文件,使用自定义加载器加载。
- 设置父加载器为null
- 设置父加载器为ExtClassLoader或BootstrapClassLoader
public class TestClassLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//扩展类加载器
ClassLoader cl = TestClassLoader.class.getClassLoader().getParent();
MyClassLoader classLoader = new MyClassLoader(cl); //父类传扩展类或根加载器 会跳过AppClassLoader加载
// ClassLoader cl = null;
// MyClassLoader classLoader = new MyClassLoader(); //父类传null 会走向根加载器加载
Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
System.out.println(aClass.getClassLoader());
Object instance = aClass.newInstance();
System.out.println(instance);
Method hello = aClass.getMethod("hello");
Object invoke = hello.invoke(instance);
System.out.println("Result:"+invoke);
}
}
输出结果:
My ClassLoader
HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@378fd1ac
HelloWorld hello method
Result:hello
4、破坏双亲委托机制
-
热部署:不停止服务的功能升级或增加新功能
- 首先要卸载掉加载该模块所有Class的类加载器,卸载类加载器会导致所有类的卸载。无法对三大类加载器进行卸载,只能通过控制自定义类加载器才能做到。
-
不在项目删除HelloWorld,加载HelloWorld
public class BrokeDelegateClassLoader extends MyClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
if (name.startsWith("java.") || name.startsWith("javax")) {
c = getSystemClassLoader().loadClass(name);
} else {
c = super.findClass(name);
if (c == null) {
try {
if (getParent() != null) {
c = getParent().loadClass(name);
} else {
c = getSystemClassLoader().loadClass(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
}
}
}
if (c == null){
throw new ClassNotFoundException("The class "+name+" not found");
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
public String toString() {
return "BrokeDelegateClassLoader";
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();
Class<?> aClass = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
System.out.println(aClass.getClassLoader());
Object instance = aClass.newInstance();
System.out.println(instance);
Method hello = aClass.getMethod("hello");
Object invoke = hello.invoke(instance);
System.out.println("Result:" + invoke);
}
}
BrokeDelegateClassLoader
HelloWorld class static code block
com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld@4783da3f
HelloWorld hello method
Result:hello
5、类加载器
5.1类加载器命名空间
-
每个加载器实例有各自的命名空间,命名空间是由该加载器及其所有父加载器所构成的,因此每个类加载器中同一个class都是独一无二的。
-
MyClassLoader命名空间
BootstrapClassLoader.ExtClassLoader.AppClassLoader.MyClassLoader
public class NameSpace {
public static void main(String[] args) throws ClassNotFoundException {
/***系统类加载器加载同一个类 */
ClassLoader classLoader = NameSpace.class.getClassLoader();
Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.lock.LockThisMain");
Class<?> bClass = classLoader.loadClass("com.today.roc.go.understand.lock.LockThisMain");
System.out.println(aClass.hashCode());
System.out.println(bClass.hashCode());
System.out.println(aClass == bClass);
/***
* 不同类加载器,加载同一个类
*/
ClassLoader cl = null;
MyClassLoader myClassLoader = new MyClassLoader(cl);
BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();
Class<?> aClass1 = myClassLoader.loadClass("com.today.roc.go.understand.oop.Son");
Class<?> aClass2 = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.oop.Son");
System.out.println(aClass1.getClassLoader());
System.out.println(aClass2.getClassLoader());
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass1 == aClass2);
/***
* 相同类型类加载器,加载同一个类
*/
ClassLoader c2 = null;
MyClassLoader myClassLoader3 = new MyClassLoader(c2);
MyClassLoader myClassLoader4 = new MyClassLoader(c2);
Class<?> aClass3 = myClassLoader3.loadClass("com.today.roc.go.understand.oop.Parent");
Class<?> aClass4 = myClassLoader4.loadClass("com.today.roc.go.understand.oop.Parent");
System.out.println(aClass3.getClassLoader());
System.out.println(aClass4.getClassLoader());
System.out.println(aClass4.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass3 == aClass4);
/***
* 769287236
* 769287236
* true
* My ClassLoader
* BrokeDelegateClassLoader
* 2121055098
* 2084435065
* false
* My ClassLoader
* My ClassLoader
* 806353501
* 2084435065
* false
*/
}
}
5.2类加载器运行时包
- 由类加载启动命名空间和类的全限定名称共同组成的
BrokeDelegateClassLoader brokeDelegateClassLoader = new BrokeDelegateClassLoader();
Class<?> aClass = brokeDelegateClassLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld");
//运行时包
BootstrapClassLoader.ExtClassLoader.AppClassLoader.MyClassLoader.BrokeDelegateClassLoader
.com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.HelloWorld
- 出于安全和封装的考虑,类加载器通过不同的运行时包,判断类之间是否可以访问,比如String void getChars是包可见,此时其它的运行时包不可见,也不会出现其它非java.lang下的类访问getChars方法
5.3初始化类加载器
-
问题:为什么开发的程序中能访问java.lang包下的类呢?
-
java.lang包下的类是由根加载器加载的,而我们开发的程序一般是由系统类加载器加载的,为什么我们在程序中能够new Object()活着new String()等任意的java.lang包下的类呢?
-
如果某个类C被类加载器CL加载,那么CL就被称为C的初始类加载器。JVM为每个类维护了一个列表,记录了将该类加载器作为类初始加载器的所有class,JVM通过列表判断是否已经被加载过来,是否需要首次加载。
-
根据JVM规范规定,类的加载过程中,所有参与的类加载器,即使没有亲自加载该类,也会被标识为该类的初始类加载器。
- SimpleClass 包含String List ArrayList 等……
public class SimpleClass {
private static byte[] buffer = new byte[8];
private static String str = "";
private static List<String> list = new ArrayList<>();
static {
buffer[0] = 1;
str = "simple";
list.add("element");
System.out.println(buffer[0]);
System.out.println(str);
System.out.println(list.get(0));
}
public static void main(String[] args) {
}
}
- 测试
public class InitClassClassLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
BrokeDelegateClassLoader classLoader = new BrokeDelegateClassLoader();
Class<?> aClass = classLoader.loadClass("com.today.roc.go.understand.thread.高并发编程详解.JVM类加载器.概念.SimpleClass");
System.out.println(classLoader.getParent());
Object o = aClass.newInstance();
}
}
输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
1
simple
element
有加载java.lang.Object List ArrayIist 等
5.4类的卸载
-
运行期间加载多少class、可以在启动JVM时指定 -verbose:class 参数观察
-
某个对象在堆内存中如果没有其它地方引用则会在GC回收,对象在堆内存中的Class对象以及Class在方法区中的数据结构何时被回收?
-
JVM规定一个Class只有在满足下面三个条件才会被GC回收,也就是类被卸载
- 该类所有实例都被GC,比如Simple.class的所有Simple实例都被回收
- 加载该类的ClassLoader实例被回收
- 该类的class实例没有在其他地方被引用
6、总结
定义同名String,在类加载时报错
prohibited package name:java.lang错误