目录
- 1. 什么是类加载器
- 2. 类加载器加载的过程
- 3. Class文件读取来源
- 4. 类加载器的分类
- 5. 那些操作会初始化类加载器
- 6. 类加载器的双亲委派机制
- 6.1 双亲委派机制机制的好处
- 7. ClassLoader源码解读
- 7.1 Launcher类源码解读
- 7.2 双亲委派机制源码分析
- 7.3 如何自定义一个类加载器
- 7.4 自定义类加载器
- 7.5 根据类加载器手写热部署插件
- 7.6 什么是SPI机制
- 7.7 如何绕开双亲委派原则
- 8. 常见Java虚拟机
- 9. 常见的几款java虚拟机
1. 什么是类加载器
将我们的class文件读取到内存中
2. 类加载器加载的过程
类加载器加载我们的class文件,并且经历过验证、准备、解析,在初始化我们该类。
3. Class文件读取来源
1.本地磁盘文件 java源代码编译的class文件
2.通过网络下载的class文件
3.War、Jar解压的class文件
4.从专门的数据库中读取的class文件
5.使用java cglib、动态代理生成的代理类class文件
Jvm虚拟机中 通过 类加载器(用户可以自定义类加载器)
4. 类加载器的分类
1.启动(Bootstrap)类加载器:加载JVM自身工作需要的类,它由JVM自己实现。它会加载AppClassLoader实现。他加载我们工程目录classpath下的class及jar包 底层是java实现
4.自定义类加载器: 也就是用户自己定义的类加载器
Launcher 源码解读
Launcher.ExtClassLoader var1;
try {
// 获取到我们扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//获取到我们应用类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 当前程序启动的线程 默认的 ClassLoader 应用类加载器
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
5. 那些操作会初始化类加载器
类的主动使用:
- 调用类的静态方法
- invokeStatic 调用静态方法
- Main
- New
- Class.formname
- 子类初始化一定会初始化父类
初始化一个类,那么一定会触发类加载器
但是类加载器加载了该类,但是该类不一定初始化。
6. 类加载器的双亲委派机制
首先在我们类加载器分为四种 自定义类加载器、应用类加载器、扩展类加载器、启动类加载器。
当一个类加载器收到请求之后,首先会依次向上查找到最顶层类加载器(启动类加载器),依次向下加载class文件,如果已经加载到class文件,子加载器不会加继续加载该class文件。
6.1 双亲委派机制机制的好处
目的就是为了防御开发者为定义的类与jdk定义源码类产生冲突问题,保证该类在内存中的唯一性。
7. ClassLoader源码解读
7.1 Launcher类源码解读
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//获取我们的扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 获取我们的应用类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 默认设置我们的类加载器是为应用类加载器
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
7.2 双亲委派机制源码分析
- ClassLoader.getSystemClassLoader().loadClass()
// 查询缓存中是否有缓存 该class
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//获取当前类加载器的父加载器 ---扩展类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果当前没有父加载器,就是为启动类加载器
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();
// 如果父加载器(扩展和启动类加载器都没有加载class,则使用当前(应用类加载器加载))
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;
7.3 如何自定义一个类加载器
public class DemoClassLoader extends ClassLoader {
private File fileObject;
public DemoClassLoader(File fileObject) {
this.fileObject = fileObject;
}
public void setFileObject(File fileObject) {
this.fileObject = fileObject;
}
public File getFileObject() {
return fileObject;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = getClassFileBytes(this.fileObject);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从文件中读取去class文件
*
* @throws Exception
*/
private byte[] getClassFileBytes(File file) throws Exception {
//采用NIO读取
FileInputStream fis = new FileInputStream(file);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
}
}
代码测试:
Class<?> aClass = new DemoClassLoader(new File("D:\\code\\com\\demo\\DemoEntity.class"))
.loadClass("com.demo.DemoEntity");
Object o = aClass.newInstance();
System.out.println(o.getClass().getClassLoader());
7.4 自定义类加载器
ClassLoader 类加载器中 双亲委派机制 核心源码部分
findLoadedClass()— 首先,检查类是否已经加载
parent.loadClass(name, false); 读取到parent.loadClass
findBootstrapClassOrNull 使用启动类加载器读取
findClass 扩展和应用类加载器、自定义类加载器
public class DemoClassLoader extends ClassLoader {
private File fileObject;
public DemoClassLoader(File fileObject) {
this.fileObject = fileObject;
}
public void setFileObject(File fileObject) {
this.fileObject = fileObject;
}
public File getFileObject() {
return fileObject;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = getClassFileBytes(this.fileObject);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 从文件中读取去class文件
*
* @throws Exception
*/
private byte[] getClassFileBytes(File file) throws Exception {
//采用NIO读取
FileInputStream fis = new FileInputStream(file);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
}
}
7.5 根据类加载器手写热部署插件
public class ClassFileEntity {
/**
* 类的名称
*/
private String name;
/**
* class
*/
private Class aClass;
/**
* 最后被更改的时间
*/
private long lastModified;
public ClassFileEntity(String name, long lastModified) {
this.name = name;
this.lastModified = lastModified;
}
public ClassFileEntity(String name, long lastModified, Class aClass) {
this.name = name;
this.lastModified = lastModified;
this.aClass = aClass;
}
public String getName() {
return name;
}
public Class getaClass() {
return aClass;
}
public long getLastModified() {
return lastModified;
}
public void setName(String name) {
this.name = name;
}
public void setaClass(Class aClass) {
this.aClass = aClass;
}
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
}
public class HotDeploymentPlug {
//存放所有的class文件
private Map<String, ClassFileEntity> mapClassFiles = new HashMap<>();
private String path;
/**
* 包的名称
*/
private String packageName = "com.demo.";
public HotDeploymentPlug(String path) {
this.path = path;
}
public void start() {
listener();
}
/**
* 监听方法
*/
public void listener() {
new Thread(() -> {
while (true) {
// 1.读取该文件下
File files = new File(path);
File[] tempList = files.listFiles();
// 2.读取class文件 存入到 mapClassFiles
for (File file :
tempList) {
String name = file.getName();
if (StringUtils.isEmpty(name)) {
continue;
}
long l = file.lastModified();
// 使用类加载器读取该 class
String className = packageName + name.replace(".class", "");
if (mapClassFiles.containsKey(className)) {
// 则比对该class文件 是否被修改
ClassFileEntity mapClassFileEntity = mapClassFiles.get(className);
if (mapClassFileEntity.getLastModified() != l) {
try {
mapClassFileEntity.setLastModified(l);
DemoClassLoader demoClassLoader = new DemoClassLoader(file);
Class<?> aClass = demoClassLoader.loadClass(className);
Object o = aClass.newInstance();
log.info(className + "class文件发生了变化");
} catch (Exception e) {
log.error("e:{}", e);
}
}
} else {
ClassFileEntity newClassFileEntity = new ClassFileEntity(className, l);
// 如果不存在 则存入到mapClassFiles集合中
mapClassFiles.put(className, newClassFileEntity);
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
public static void main(String[] args) {
HotDeploymentPlug hotDeploymentPlug = new HotDeploymentPlug("D:\\code\\com\\demo");
hotDeploymentPlug.listener();
}
}
7.6 什么是SPI机制
Java SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.
实现方式:
- 首先需要再resources目录下:创建文件夹META-INF services
- 定义接口文件的名称:
D:\code\demo_jvm\src\main\resources\META-INF\services\com.demo.service.MyService
名称规范:包名+类名组成。
com.demo.service.impl.MyServiceImpl01
com.demo.service.impl.MyServiceImpl02
ServiceLoader<MyService> load = ServiceLoader.load(MyService.class);
load.forEach((t) -> {
System.out.println(t.get());
});
获取当前线程对应的应用类类加载器,加载该class。
ServiceLoader<MyService> load = ServiceLoader.load(MyService.class);
load.forEach((t) -> {
System.out.println(t.get());
});
7.7 如何绕开双亲委派原则
// Thread.currentThread().setContextClassLoader(Test02.class.getClassLoader().getParent());
// Connection root =
// DriverManager
// .getConnection
// (
// "jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8",
// "root", "root");
// Class.forName("com.mysql.jdbc.Driver");
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
Driver next = driversIterator.next();
System.out.println(next);
}
} catch(Throwable t) {
// Do nothing
}
8. 常见Java虚拟机
(1)HotSpot VM
HotSpot VM是目前主流的虚拟机。像Oracle / Sun JDK、OpenJDK的各种变种(例如IcedTea、Zulu),用的都是相同核心的HotSpot VM。从Java SE 7开始,HotSpot VM就是Java规范的“参考实现”,JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。这个合并并不是要把JRockit的部分代码插进HotSpot里,而是把前者一些有价值的功能在后者里重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分。
(2)J9 VM
J9是IBM开发的一个高度模块化的JVM。J9 VM的性能水平大致跟HotSpot VM是一个档次的。
(3)JRockit
以前Java SE的主流JVM中还有JRockit,跟HotSpot与J9一起并称三大主流JVM。这三家的性能水平基本都在一个水平上,竞争很激烈。自从Oracle把BEA和Sun都收购了之后,Java SE JVM只能二选一,JRockit就炮灰了。JRockit最后发布的大版本是R28,只到JDK6,原本在开发中的R29及JDK7的对应功能都没来得及完成项目就被终止了。
9. 常见的几款java虚拟机
SUN Classic VM:第一款商用java虚拟机,1996年1月jdk1.0中带的java虚拟机,只能使用纯解释器的方式来执行java代码
Exact VM:准确式内存管理,编译器和解释器混合工作以及两级即时编译 ,只在Solaris平台发布
HotSport VM:即时编译,节约了时间和存储,称霸武林
KVM:简单,轻量,高可以执行,主要在手机平台使用
JRockit:BEA,世界上最快的java虚拟机,专注服务端应用,优势:垃圾回收机制,MissionControl服务套件
j9:IBM Technology for java virtual Machines IT4J
dalvik:不能直接指向class文件,寄存器架构,执行dex文件,由class文件转化而来
MicrosoftJvm:只能在windows平台运行,
高性能java虚拟机
Azul VM:专用虚拟机,经HotSport改进得来,运行在本公司专有硬件中
Liquid VM:不需要操作系统的支持
taobao虚拟机:淘宝深度定制的产品,硬件依赖性比较高