從字節碼驗證java類的加載過程

1.1 編譯CLPreparation.java文件並且反編譯生成字節碼

CLPreparation的源碼:

package com.iqiyi.jvm.serializable;

/**
 * Created by leixingbang on 2019/8/1.
 */
public class CLPreparation {
    public static int a = 111111;//靜態變量 
    public static final int INT_CONSTANT = 22222;//原始類型常量 
    public static final Integer INTEGER_CONSTANT = Integer.valueOf(3333333);//引用類型
}

編譯並使用javap -v 命令反編譯.class文件生成字節碼

[root@audit-video-history-1f6396079-1 ~]# javac CLPreparation.java 
[root@audit-video-history-1f6396079-1 ~]# ll
total 24
-rw-r--r-- 1 root root  509 Aug  2 12:12 CLPreparation.class
-rw-r--r-- 1 root root  286 Aug  1 19:37 CLPreparation.java
[root@audit-video-history-1f6396079-1 ~]# javap -v CLPreparation.class 
Classfile /root/CLPreparation.class

抓取字節碼的源關鍵部分源文件,從源文件中可以看出靜態變量值的真正初始化(對a進行11111賦值),需要調用putstatic的jvm指令,而使用該指令是在顯示初始化時候執行的。加載—驗證–準備—解析–初始化–卸載

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #2                  // int 111111
         2: putstatic     #3                  // Field a:I
         5: ldc           #4                  // int 3333333
         7: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        10: putstatic     #6                  // Field INTEGER_CONSTANT:Ljava/lang/Integer;
        13: return
      LineNumberTable:
        line 7: 0
    

1.2雙親委託模型

作用是避免重複加載,造成浪費。

1.3類加載機制的三個特性

  • 雙親委派模型
  • 可見性 子類加載器可以看到父類加載器的類型,反過來不可以。
  • 單一性 父加載器的類型對子類可見,所以父加載器加載過類型子類不會重複加載。

1.4 自定義類的加載過程

  • 步驟(1)通過定義的名稱'com.iqiyi.leetcode.CheckBracketsTest'找到對應二進制的實現‘CheckBracketsTest.class’,這裏就可以截取,修改字節碼
    步驟(2)調用父類的define class 方法,完成類的加載過程。
    

    自定義實現Classloader類:

    
    /**
     * 文件加載類
     * 可根據MyFileClassLoader 從文件中動態生成類
     *
     * @author chengmingwei
     */
    public class MyFileClassLoader extends ClassLoader {
    
        private String classPath;
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            MyFileClassLoader fileClsLoader = new MyFileClassLoader();
            fileClsLoader.setClassPath("D:\\myPoject\\leetcode\\target\\test-classes\\");
            Class cls = fileClsLoader.loadClass("com.iqiyi.leetcode.CheckBracketsTest");
            Object obj = cls.newInstance();
            Method[] mthds = cls.getMethods();
            for (Method mthd : mthds) {
                String methodName = mthd.getName();
                System.out.println("mthd.name=" + methodName);
            }
            System.out.println("obj.class=" + obj.getClass().getName());
            System.out.println("obj.class=" + cls.getClassLoader().toString());
            System.out.println("obj.class=" + cls.getClassLoader().getParent().toString());
        }
    
        /**
         * 根據類名字符串從指定的目錄查找類,並返回類對象
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = null;
            try {
                //步驟(1)通過定義的名稱'com.iqiyi.leetcode.CheckBracketsTest'找到對應二進制的實現‘CheckBracketsTest.class’,這裏就可以截取,修改字節碼
                classData = loadClassData(name);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //步驟(2)調用父類的define class 方法,完成類的加載過程。
            return super.defineClass(name, classData, 0, classData.length);//
    
        }
    
        /**
         * 根據類名字符串加載類 byte 數據流
         *
         * @param name 類名字符串  例如: com.cmw.entity.SysEntity
         * @return 返回類文件 byte 流數據
         * @throws IOException
         */
        private byte[] loadClassData(String name) throws IOException {
            File file = getFile(name);
            FileInputStream fis = new FileInputStream(file);
            byte[] arrData = new byte[(int) file.length()];//一次性讀取文件到二進制數組
            fis.read(arrData);
            return arrData;
        }
    
        /**
         * 根據類名字符串返回一個 File 對象
         *
         * @param name 類名字符串
         * @return File 對象
         * @throws FileNotFoundException
         */
        private File getFile(String name) throws FileNotFoundException {//獲取文件
            File dir = new File(classPath);
            if (!dir.exists()) throw new FileNotFoundException(classPath + " 目錄不存在!");
            String _classPath = classPath.replaceAll("[\\\\]", "/");
            int offset = _classPath.lastIndexOf("/");
            name = name.replaceAll("[.]", "/");
            if (offset != -1 && offset < _classPath.length() - 1) {
                _classPath += "/";
            }
            _classPath += name + ".class";
            dir = new File(_classPath);
            if (!dir.exists()) throw new FileNotFoundException(dir + " 不存在!");
            return dir;
        }
    
        public String getClassPath() {
            return classPath;
        }
    
        public void setClassPath(String classPath) {
            this.classPath = classPath;
        }
    }
    

1.5 從類的加載角度優化java啓動(啓動爲重點)速度。

字節碼(.class文件)與平臺無關,但是二進制文件加載 需要加載驗證解析初始化 這些步驟會導致java的啓動速度變慢。

提速的方式:

  • AOT 。編譯時直接編譯爲機器碼,降低解析的開銷。
  • AppCDS.將通過內存映射直接映射到內存的地址空間,而不是走加載延、驗證解析等步驟。

1.6 java hell 包衝突

同一個類在不同的jar包中有不同的版本。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章