共计 7510 个字符,预计需要花费 19 分钟才能阅读完成。
1. 类加载初始化
- Loading
把一个 class 文件加载到内存
-
Linking
- Verification
校验 class 文件符不合乎 class 文件规范
- Preparation
动态变量赋默认值 static int count = 10;
在这一步 count = 0 默认值
- Resolution
符号援用转换为内存地址 能够间接拜访的地址
- Verification
- Initializing
动态变量赋值为初始值
2. 类加载器
1. JVM 是按需动静加载采纳双亲委派机制
用 getClassLoader 获取类加载器 如果是 Null 那就是到了 BootStrap 类加载器 了 因为是 C ++ 实现的 木有对应类
咱们平时写的类 就再 classpath 下 所以是由 App 类加载器加载的
留神:这四个类加载器没有继承关系 只是程序关系
父加载器不是父类,只是说本人加载不了了,就交给上一级加载
他们不是继承关系 那是怎么产生分割?用组合啦~~
2. 步骤 1 图是不是不能阐明双亲委派?我给你一张图 让你明确一下双亲委派
不 ! 我为什么要用图?我先用一句话形容:classLoader 在本人的 cache 缓存中找是不是曾经加载过类 XX, 如果没有就找他的父类加载器,父类加载器在本人的 cache 缓存中找是不是曾经加载过类 XX, 找到就返回,找不到就接着找父类加载器,直到 BootStrap 如果最初还没在 cache 中找到 那就从父类加载器往回找,父加载器问儿子你看看你负责的 jar 包什么的 有没有这个类,有的话你就加载到内存,没有的话你就找你儿子类加载器去看,始终看看看看,看到最初如果有返回最小的类加载器,还是没找见 那就抛异样吧 ClassNotFoundException
我还是屈从了,上图吧。
/**
* @author 木子的昼夜
*/
public class MyClassLoaderTest {public static void main(String[] args) throws ClassNotFoundException {MyClassLoaderTest.class.getClassLoader().loadClass("classtest.MyClassLoaderTest");
}
}
// ClassLoader#loadClass
public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
}
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 {
// 如果没有了 那就是最初一层了 在本人的管辖范畴 找
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();
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. 一个 class 文件被 load 到内存里是什么样
4. 想晓得某个类是被哪个类加载器加载到内存里的 getClassLoader()
System.out.println(String.class.getClassLoader());
// null --> BootStrap 类加载器
System.out.println(sun.awt.HKSCS.class.getClassLoader());
// null --> BootStrap 类加载器
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
// sun.misc.Launcher$ExtClassLoader@1d44bcfa --> Ext
System.out.println(ClassLoaderTest.class.getClassLoader());
// sun.misc.Launcher$AppClassLoader@18b4aac2--> App
5. 为啥搞双亲委派
- 为了平安 如果没有双亲委派 间接从自定义类加载器找 class(次要问题)
(1) 如果这里我自定义一个 java.lang.String
我在 String 类里边写一段挖矿脚本
(2) 而后小月月下载了我的类库 筹备应用!
他用到了 String,他认为这是 jdk 的类,后果我预判了他的预判,这个 String 类曾经是我的了,由我掌控
(3) 双亲委派就能够防止这个问题,因为零碎始终会先去上一层看有没有这个类 因为上一层有了 所以就不 会应用我定义的 String 类了
- 如果父层加载了 我就不必加载了 再加载就是浪费资源了(主要问题)
6. 既然自定义了 不是我说什么就是什么吗?
不是!类加载器曾经限度死了
7. 类加载器父子关系示例 不是继承 是组合
看下边代码,读者能够一试 再认真品一品 这句话:加载类加载器的类加载器 不是类加载器的父类加载器
System.out.println(ParentDemo.class.getClassLoader());
System.out.println(ParentDemo.class.getClassLoader().getClass().getClassLoader());
System.out.println(ParentDemo.class.getClassLoader().getParent());
System.out.println(ParentDemo.class.getClassLoader().getParent().getParent());
输入后果:sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@6e0be858
null
8. 各个类加载器范畴
能够看一眼 sun.misc.Launcher 源码
- BootstrapClassLoader : sun.boot.class.path
- ExtensionClassLoader: java.ext.dirs
- AppClassLoader:java.class.path
public static void main(String[] args) {System.out.println("---------BOOT-----------------------");
String boot = System.getProperty("sun.boot.class.path");
Arrays.stream(boot.split(";")).forEach(s-> System.out.println(s));
System.out.println("--------------Ext-------------------");
String ext = System.getProperty("java.ext.dirs");
Arrays.stream(ext.split(";")).forEach(s-> System.out.println(s));
System.out.println("----------APP-----------------------");
String app = System.getProperty("java.class.path");
Arrays.stream(app.split(";")).forEach(s-> System.out.println(s));
}
输入后果:---------BOOT-----------------------
C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_144\jre\classes
--------------Ext-------------------
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
----------APP-----------------------
C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar
E:\workspace_idea\blogmaven\target\classes
// 读者留神看这一个 这个是以后我的项目 class 文件地址
9. 自定义类加载器
-
先定一个一个被加载的 java 类 Test.java
public class Test {public void say(){System.out.println("胜利喽!"); } }
-
编译 Test.java 到指定目录
javac -encoding utf-8 -d E:/classes Test.java
-
继承 ClassLoader、重写模板办法 findClass、调用 defineClass
/** * @author 木子的昼夜 */ public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { FileInputStream inputStream = null; ByteArrayOutputStream out = null; try { // 类名 aa.xx 转换为 aa/xx.class File file = new File("E:\\classes\\",name.replaceAll("\\.","/").concat(".class")); // 获取输出流 inputStream = new FileInputStream(file); // 输入字节流 out = new ByteArrayOutputStream(); // 转换为字节数组 int available = inputStream.available(); byte[] byteArr = new byte[available]; inputStream.read(byteArr); // 生成 class return defineClass(name,byteArr,0,byteArr.length); }catch (Exception e) {throw new ClassNotFoundException(); }finally { // 敞开流 if (inputStream != null) { try {inputStream.close(); } catch (IOException e) {e.printStackTrace(); } } if (out != null) { try {out.close(); } catch (IOException e) {e.printStackTrace(); } } } } }
-
测试
public static void main(String[] args) { try {Class<?> clazz = new MyClassLoader().loadClass("Test"); // 获取申明的办法 Method say = clazz.getDeclaredMethod("say"); // 创立实例 Object instance = clazz.newInstance(); // 调用办法 say.invoke(instance); }catch (Exception e) {e.printStackTrace(); } } 输入后果:胜利喽!
-
自定义类加载器加载自加密的 class
- 文件是你本人的,你在编译完了之后能够通过某种算法加密一下,
等你用自定义 ClassLoader 加载的时候,你能够本人个儿解密 - 能够避免反编译
- 能够避免被篡改
- 文件是你本人的,你在编译完了之后能够通过某种算法加密一下,
10. 混合编译
- 解释器:bytecode intepreter
-
JIT: Just In – Time compiler
1. -Xmixed 默认为混合模式 开始应用解释执行 启动速度快 对热点代码进行实时检测和编译 2. -Xint 应用解释模式 启动快 执行慢 3. -Xcomp 应用纯编译模式 启动慢 运行快
-
混合模式
1. 解释器 + 热点代码编译 2. 起始阶段采纳解释器 3. 热点代码检测 检测到了应用热点代码编译 1. 屡次被调用的办法(办法计数器:监测办法执行频率)2. 屡次被调用的循环(循环计数器:监测循环执行频率)
未完 待续 …
本文有很多图,如果不分明的话,大家能够关注公众号:木子的昼夜
发送 ” 类加载 ” 即可取得高清图拜访地址
发送 ” 路线 ” 即可取得本系列文章纲要
也可发送本人想问的问题给我,我会在看到的第一工夫回复
最初附上本人公众号刚开始写 愿一起提高:
留神 :以上文字 仅代表个人观点,仅供参考,如有问题还请指出,立刻马上连滚带爬的从被窝里进去改过。