写点什么

话说 类加载过程 第一篇

发布于: 2021 年 03 月 11 日
话说 类加载过程 第一篇

1. 类加载初始化


  1. Loading


把一个 class 文件加载到内存


  1. Linking


1. Verification


校验 class 文件符不符合 class 文件标准


2. Preparation


静态变量赋默认值 static int count = 10;


在这一步 count = 0 默认值


3. Resolution


符号引用转换为内存地址 可以直接访问的地址


  1. 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#loadClasspublic 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 --> ExtSystem.out.println(ClassLoaderTest.class.getClassLoader());// sun.misc.Launcher$AppClassLoader@18b4aac2--> App
复制代码


5. 为啥搞双亲委派


  1. 为了安全 如果没有双亲委派 直接从自定义类加载器找 class (主要问题)



​ (1) 如果这里我自定义一个 java.lang.String

我在 String 类里边写一段挖矿脚本


​ (2) 然后小月月下载了我的类库 准备使用 !


​ 他用到了 String ,他以为这是 jdk 的类,结果我预判了他的预判,这个 String 类已经是我的了,由我掌控


​ (3) 双亲委派就可以避免这个问题,因为系统始终会先去上一层看有没有这个类 因为上一层有了 所以就不 会使用我定义的 String 类了


  1. 如果父层加载了 我就不用加载了 再加载就是浪费资源了 (次要问题)


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@18b4aac2nullsun.misc.Launcher$ExtClassLoader@6e0be858null
复制代码


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.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\sunrsasign.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jarC:\Program Files\Java\jdk1.8.0_144\jre\classes--------------Ext-------------------C:\Program Files\Java\jdk1.8.0_144\jre\lib\extC:\WINDOWS\Sun\Java\lib\ext----------APP-----------------------C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jarC:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jarE:\workspace_idea\blogmaven\target\classes // 读者注意看这一个 这个是当前项目class文件地址
复制代码


9. 自定义类加载器


  1. 先定一个一个被加载的 java 类 Test.java

   public class Test {       public void say(){
System.out.println("成功喽!");
}
}
复制代码


  1. 编译 Test.java 到指定目录

javac  -encoding utf-8  -d E:/classes  Test.java 
复制代码


  1. 继承 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();

}
}
}
}
}
复制代码


  1. 测试

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();
}
}
输出结果:
成功喽!
复制代码


  1. 自定义类加载器加载自加密的 class


1. 文件是你自己的,你在编译完了之后可以通过某种算法加密一下,

等你用自定义 ClassLoader 加载的时候,你可以自己个儿解密

2. 可以防止反编译

3. 可以防止被篡改


10. 混合编译


1. 解释器 : bytecode intepreter


2. JIT: Just In - Time compiler


1. -Xmixed 默认为混合模式

开始使用解释执行 启动速度快

对热点代码进行实时检测和编译

2. -Xint 使用解释模式 启动快 执行慢

3. -Xcomp 使用纯编译模式 启动慢 运行快


3. 混合模式


1. 解释器+热点代码编译


2. 起始阶段采用解释器


3. 热点代码检测 检测到了使用热点代码编译


1. 多次被调用的方法(方法计数器:监测方法执行频率)

2. 多次被调用的循环(循环计数器:监测循环执行频率)


未完 待续...


本文有很多图,如果不清楚的话,大家可以关注公众号:木子的昼夜


​ 发送"类加载" 即可获得高清图访问地址


​ 发送"路线" 即可获得本系列文章大纲


​ 也可发送自己想问的问题给我,我会在看到的第一时间回复


最后附上自己公众号刚开始写 愿一起进步:



注意: 以上文字 仅代表个人观点,仅供参考,如有问题还请指出,立即马上连滚带爬的从被窝里出来改正。


用户头像

还未添加个人签名 2018.03.28 加入

还未添加个人简介

评论

发布
暂无评论
话说 类加载过程 第一篇