一 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包中有不同的版本。