话说 类加载过程 第一篇

2021-03-11 17:59:11 浏览数 (1)

1. 类加载初始化

  1. Loading

把一个class文件加载到内存

  1. Linking
  2. Verification
代码语言:txt复制
  校验class文件符不符合class文件标准  
  1. Preparation
代码语言:txt复制
  静态变量赋默认值 static int count = 10;  
代码语言:txt复制
  在这一步count = 0  默认值 
  1. Resolution
代码语言:txt复制
  符号引用转换为内存地址  可以直接访问的地址
  1. Initializing

静态变量赋值为初始值

类加载过程.png类加载过程.png

2. 类加载器

1. JVM是按需动态加载采用双亲委派机制

用getClassLoader获取类加载器 如果是Null 那就是到了BootStrap类加载器 了 因为是C 实现的 木有对应类

我们平时写的类 就再classpath下 所以是由App类加载器加载的

注意: 这四个类加载器没有继承关系 只是顺序关系

父加载器不是父类,只是说自己加载不了了,就交给上一级加载

他们不是继承关系 那是怎么产生联系? 用组合啦~~

classloader.pngclassloader.png
2. 步骤1图是不是不能说明双亲委派? 我给你一张图 让你明白一下双亲委派

不 ! 我为什么要用图? 我先用一句话描述:classLoader在自己的cache缓存中找是不是已经加载过类XX,如果没有就找他的父类加载器,父类加载器在自己的cache缓存中找是不是已经加载过类XX,找到就返回,找不到就接着找父类加载器,直到BootStrap 如果最后还没在cache中找到 那就从父类加载器往回找,父加载器问儿子你看看你负责的jar包什么的 有没有这个类,有的话你就加载到内存,没有的话你就找你儿子类加载器去看,一直看看看看,看到最后如果有返回最小的类加载器,还是没找见 那就抛异常吧 ClassNotFoundException

代码语言:txt复制
     我还是屈服了,上图吧。
双亲委派.png双亲委派.png
代码语言:txt复制
/**
 * @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到内存里是什么样
load到内存后什么样.pngload到内存后什么样.png
4. 想知道某个类是被哪个类加载器加载到内存里的 getClassLoader()
代码语言:txt复制
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. 为啥搞双亲委派
  1. 为了安全 如果没有双亲委派 直接从自定义类加载器找class (主要问题)
双亲委派安全.png双亲委派安全.png
代码语言:txt复制
 (1)  如果这里我自定义一个java.lang.String 
代码语言:txt复制
     我在String类里边写一段挖矿脚本
代码语言:txt复制
 (2) 然后小月月下载了我的类库 准备使用 !
代码语言:txt复制
    他用到了String ,他以为这是jdk的类,结果我预判了他的预判,这个String类已经是我的了,由我掌控 
代码语言:txt复制
(3) 双亲委派就可以避免这个问题,因为系统始终会先去上一层看有没有这个类 因为上一层有了 所以就不  会使用我定义的String类了
  1. 如果父层加载了 我就不用加载了 再加载就是浪费资源了 (次要问题)
6. 既然自定义了 不是我说什么就是什么吗 ?
代码语言:txt复制
不是! 类加载器已经限制死了  
7. 类加载器父子关系示例 不是继承 是组合

看下边代码,读者可以一试 再仔细品一品 这句话: 加载类加载器的类加载器 不是类加载器的父类加载器

代码语言:txt复制
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
代码语言:txt复制
 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 FilesJavajdk1.8.0_144jrelibresources.jar
C:Program FilesJavajdk1.8.0_144jrelibrt.jar
C:Program FilesJavajdk1.8.0_144jrelibsunrsasign.jar
C:Program FilesJavajdk1.8.0_144jrelibjsse.jar
C:Program FilesJavajdk1.8.0_144jrelibjce.jar
C:Program FilesJavajdk1.8.0_144jrelibcharsets.jar
C:Program FilesJavajdk1.8.0_144jrelibjfr.jar
C:Program FilesJavajdk1.8.0_144jreclasses
--------------Ext-------------------
C:Program FilesJavajdk1.8.0_144jrelibext
C:WINDOWSSunJavalibext
----------APP-----------------------
C:Program FilesJavajdk1.8.0_144jrelibcharsets.jar
C:Program FilesJavajdk1.8.0_144jrelibdeploy.jar
C:Program FilesJavajdk1.8.0_144jrelibextaccess-bridge-64.jar
C:Program FilesJavajdk1.8.0_144jrelibextcldrdata.jar
C:Program FilesJavajdk1.8.0_144jrelibextdnsns.jar
C:Program FilesJavajdk1.8.0_144jrelibextjaccess.jar
C:Program FilesJavajdk1.8.0_144jrelibextjfxrt.jar
C:Program FilesJavajdk1.8.0_144jrelibextlocaledata.jar
C:Program FilesJavajdk1.8.0_144jrelibextnashorn.jar
C:Program FilesJavajdk1.8.0_144jrelibextsunec.jar
C:Program FilesJavajdk1.8.0_144jrelibextsunjce_provider.jar
C:Program FilesJavajdk1.8.0_144jrelibextsunmscapi.jar
C:Program FilesJavajdk1.8.0_144jrelibextsunpkcs11.jar
C:Program FilesJavajdk1.8.0_144jrelibextzipfs.jar
C:Program FilesJavajdk1.8.0_144jrelibjavaws.jar
C:Program FilesJavajdk1.8.0_144jrelibjce.jar
C:Program FilesJavajdk1.8.0_144jrelibjfr.jar
C:Program FilesJavajdk1.8.0_144jrelibjfxswt.jar
C:Program FilesJavajdk1.8.0_144jrelibjsse.jar
C:Program FilesJavajdk1.8.0_144jrelibmanagement-agent.jar
C:Program FilesJavajdk1.8.0_144jrelibplugin.jar
C:Program FilesJavajdk1.8.0_144jrelibresources.jar
C:Program FilesJavajdk1.8.0_144jrelibrt.jar
E:workspace_ideablogmaventargetclasses 
// 读者注意看这一个  这个是当前项目class文件地址
    
9. 自定义类加载器
  1. 先定一个一个被加载的java类 Test.java
代码语言:txt复制
  public class Test {
       public void say(){
           System.out.println("成功喽!");
       }
   }
  1. 编译Test.java 到指定目录 javac -encoding utf-8 -d E:/classes Test.java
  2. 继承ClassLoader 、 重写模板方法findClass 、调用defineClass
代码语言:txt复制
/**
    * @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. 测试
代码语言:txt复制
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
代码语言:txt复制
      	1.  -Xmixed 默认为混合模式 
      开始使用解释执行 启动速度快
      对热点代码进行实时检测和编译
      	2.  -Xint 使用解释模式 启动快 执行慢
      	3.  -Xcomp 使用纯编译模式 启动慢 运行快 
  1. 混合模式
代码语言:txt复制
 1. 解释器 热点代码编译
代码语言:txt复制
 2. 起始阶段采用解释器
代码语言:txt复制
 3. 热点代码检测 检测到了使用热点代码编译
代码语言:txt复制
     	1.  多次被调用的方法(方法计数器:监测方法执行频率)
            	2.  多次被调用的循环(循环计数器:监测循环执行频率)
未完 待续...

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

代码语言:txt复制
	发送"类加载"  即可获得高清图访问地址
代码语言:txt复制
	发送"路线" 即可获得本系列文章大纲 
代码语言:txt复制
	也可发送自己想问的问题给我,我会在看到的第一时间回复
最后附上自己公众号刚开始写 愿一起进步:
公众号二维码.jpg公众号二维码.jpg

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

0 人点赞