相关链接
JVM虚拟机 + 执行引擎 + 本地方法接口
概述
类加载:我们都知道Java代码会被编译成class文件,在class文件中描述了该类的各种信息,class类最终需要被加载到虚拟机中才能运行和使用。
类加载机制:虚拟机把Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机可以直接使用的Java类型。
加载来源
package com.groupies.base.JVM;
import java.util.Arrays;
import java.util.List;
/**
* @author GroupiesM
* @date 2021/1/29
* 类加载器的分类:1.启动(Bootstrap)类加载器
*/
public class bootClassLoaderLoadingPath {
public static void main(String[] args) {
//获取启动列加载器加载的目录
String bootStrapLoadingPath=System.getProperty("sun.boot.class.path");
//把加载的目录转为集合
List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
for (String bootPath:bootLoadingPathList){
/*
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\resources.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\rt.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\sunrsasign.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jsse.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jce.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\charsets.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jfr.jar
启动类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\classes
*/
System.out.println("启动类加载器加载的目录:"+bootPath);
}
}
}
文件名 | 描述 |
---|---|
rt.jar | 运行环境包,rt即runtime,J2SE 的类定义都在这个包内 |
charsets.jar | 字符集支持包 |
jce.jar | 是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现 |
jsse.jar | 安全套接字拓展包Java™ Secure Socket Extension |
classlist | 该文件内表示是引导类加载器应该加载的类的清单 |
net.properties | JVM 网络配置信息 |
java.ext.dirs
,我们同样写一段代码去加载它:
sun.misc.Launcher$ExtClassLoader
)实现的。Java类,继承自URLClassLoader 扩展类加载器。JAVA_HOME /lib/ext
或者由系统变量-Djava.ext.dir
指定位置中的类库加载到内存中。package com.groupies.base.JVM;
import java.util.Arrays;
import java.util.List;
/**
* @author GroupiesM
* @date 2021/1/29
* 类加载器的分类:2.启动(Bootstrap)类加载器
*/
public class extClassLoaderLoadingPath {
public static void main(String[] args) {
//获取启动列加载器加载的目录
String bootStrapLoadingPath = System.getProperty("java.ext.dirs");
//把加载的目录转为集合
List<String> bootLoadingPathList = Arrays.asList(bootStrapLoadingPath.split(";"));
for (String bootPath : bootLoadingPathList) {
/*
拓展类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext
拓展类加载器加载的目录:C:\WINDOWS\Sun\Java\lib\ext
*/
System.out.println("拓展类加载器加载的目录:" + bootPath);
}
}
}
java.class.path
package com.groupies.base.JVM;
import java.util.Arrays;
import java.util.List;
/**
* @author GroupiesM
* @date 2021/1/29
* 类加载器的分类:3.应用程序(Application)类加载器
*/
public class appClassLoaderLoadingPath {
public static void main(String[] args) {
//获取启动列加载器加载的目录
String bootStrapLoadingPath=System.getProperty("java.class.path");
//把加载的目录转为集合
List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
for (String bootPath:bootLoadingPathList){
/*
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\charsets.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\deploy.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\access-bridge-64.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\cldrdata.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\dnsns.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\jaccess.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\jfxrt.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\localedata.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\nashorn.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\sunec.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\sunjce_provider.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\sunmscapi.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\sunpkcs11.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\ext\zipfs.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\javaws.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jce.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jfr.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jfxswt.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\jsse.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\management-agent.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\plugin.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\resources.jar
应用程序类加载器加载的目录:D:\develop\Java\JAVA_HOME\jdk1.8\jre\lib\rt.jar
应用程序类加载器加载的目录:F:\bakup\JAVA_PRACTICE\base01\target\classes
应用程序类加载器加载的目录:D:\develop\Java\Maven\maven_repository\org\springframework\spring-test\4.1.3.RELEASE\spring-test-4.1.3.RELEASE.jar
应用程序类加载器加载的目录:D:\develop\Java\Maven\maven_repository\org\springframework\spring-core\4.1.3.RELEASE\spring-core-4.1.3.RELEASE.jar
应用程序类加载器加载的目录:D:\develop\Java\Maven\maven_repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar
应用程序类加载器加载的目录:D:\develop\Java\Maven\maven_repository\junit\junit\4.12\junit-4.12.jar
应用程序类加载器加载的目录:D:\develop\Java\Maven\maven_repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar
应用程序类加载器加载的目录:D:\develop\Java\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar
*/
System.out.println("应用程序类加载器加载的目录:"+bootPath);
}
}
}
=> ClassLoader详解
java.lang.ClassLoader
的子类自定义加载class,系统JVM自带的3个ClassLoader只会加载指定目录下的class文件,如果某个情况下,我们需要加载应用程序之外的类文件呢?比如本地D盘下的,或者去加载网络上的某个类文件,这种情况就可以自定义一个ClassLoader。而且我们可以根据自己的需求,对class文件进行加密和解密。java.lang.ClassLoader
,重写它的findClass方法。protected Class<?> loadClass(String name,
boolean resolve)
throws ClassNotFoundException
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) {
//父加载器不为空,调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则,调用Bootstrap Classloader
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();
//父加载器没有找到,则调用findclass
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()
resolveClass(c);
}
return c;
}
}
package com.test;
public class Test {
public void say() {
System.out.println("Hello MyClassLoader");
}
}
在Test.java所在目录打开dos窗口
编译Test.java,生成.class字节码文件
自定义加载类 MyClassLoader
package com.groupies.base.JVM.CustomClassLoader;
import java.io.*;
/**
* @author GroupiesM
* @date 2021/2/1
* 自定义(Custom)类加载器
* 通过 java.lang.ClassLoader的子类自定义加载class,系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader。而且我们可以根据自己的需求,对class文件进行加密和解密。
*
* 如何自定义ClassLoader:
* 新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。
* 将class字节码数组转换为Class类的实例
* 调用loadClass方法即可
*/
public class MyClassLoader extends ClassLoader {
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classDate = getDate(name);
if (classDate == null) {
} else {
//defineClass方法将字节码转化为类
return defineClass(name, classDate, 0, classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
/**
返回类的字节码
*/
private byte[] getDate(String className) throws IOException {
InputStream in = null;
ByteArrayOutputStream out = null;
String path = classpath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
in.close();
out.close();
}
return null;
}
}
package com.groupies.base.JVM.CustomClassLoader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author GroupiesM
* @date 2021/2/1
*/
public class TestMyClassLoader {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
//自定义类加载器的加载路径
MyClassLoader myClassLoader = new MyClassLoader("D:\\lib");
//包名+类名
Class c = myClassLoader.loadClass("com.test.Test");
if (c != null) {
Object obj = c.newInstance();
Method method = c.getMethod("say", null);
method.invoke(obj, null);
//输出结果:
//Hello MyClassLoader
//com.groupies.base.JVM.CustomClassLoader.MyClassLoader@5cad8086
System.out.println(c.getClassLoader().toString());
}
}
}
概述
Java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用来获取和设置线程的上下文类加载器。setContextClassLoader(ClassLoader cl)
方法进行设置的话,线程将继承其父线程的线程上下文(Thread Context)类加载器。作用
所谓双亲委派模型,就是指一个类接收到类加载请求后,会把这个请求依次传递给父类加载器(如果还有的话),如果顶层的父类加载器可以加载,就成功返回,如果无法加载,再依次给子加载器去加载。
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
AppClassLoader
;ExtClassLoader
;null
;package com.groupies.base.JVM;
/**
* @author GroupiesM
* @date 2021/1/29
* 类加载器:双亲委派机制
* 目标:编写一个类,依次输出这个类的类加载器,父类加载器,父类的父类加载器
*/
public class ClassLoaderPath {
public static void main(String[] args) {
//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(ClassLoaderPath.class.getClassLoader());
//sun.misc.Launcher$ExtClassLoader@2503dbd3
System.out.println(ClassLoaderPath.class.getClassLoader().getParent());
//null
System.out.println(ClassLoaderPath.class.getClassLoader().getParent().getParent());
}
}
ClassLoader类源码
双亲委派模型流程
双亲委派模型流程图
为什么要双亲委派
java.lang.Object
类放在classpath下,那应用程序就乱套了。双亲委派模型并不是绝对的,spi机制就可以打破双亲委派模型。
jre/lib/rt.jar
里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。META-INF/services/java.sql.Driver
文件里边的类到JVM内存,完成驱动的自动加载。java.util.ServiceLoader
的API文档里有比较详细的介绍。 SPI机制是一种破坏双亲委派模型的自动加载技术,用于加载接口的具体实现类。META-INF/services/
目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/
里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
。JDBC SPI mysql的实现如下所示。
21/02/18
M