类加载机制
代码运行过程
我们写的 “.java” 文件打包成 jar、war 文件后就部署到服务器上去运行了。可能是 tomcat 这样的容器,也可能是 java 命令来启动。
但其实我们用反编译工具就可以看到,打包后的文件是 “.class” 文件,一旦启动这个项目,其实就是启动一个jvm来运行这个系统,由类加载器把这些 “.class” 字节码文件加载到jvm中。
最后jvm就会基于自己的字节码执行引擎,来执行加载到内存里我们写好的那些类了。它需要哪个类的时候,就会使用类加载器来加载对应的类。
类加载过程
一个类从加载到使用,一般会经历经历这几个过程: 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
加载
jvm通过类加载器把java文件加载到内存中并生成一个class文件。
类加载的时机
1 创建类的实例,也就是new一个对象
2 访问类或接口的静态变量、静态方法时
3 反射(Class.forName("com.lyj.load"))
4 初始化一个类的子类(会首先初始化子类的父类)
验证
class文件被加载进来之后,java虚拟机规范就会校验文件类容,来判断文件是否符合语法规范。比如你的 “.class" 文件被人篡改了,字节码都不符合规范,那么jvm肯定是不能执行这些验证码的啊。只有验证通过,才能交给jvm去运行。
准备
语法校验成功之后,就会给类变量分配内存、赋予虚拟机初始值。比如此时id默认值就是0,name默认值就是null。
解析
就是将符号引用转换为直接引用的过程。
初始化
和准备阶段的赋值不一样,主要是程序对类的变量指定代码初始值,执行静态代码块。现在才真正给把order.getUserId()值给了id,张三给了name。
类加载器种类
启动类加载器(Bootstarp ClassLoader)
我们运行java代码就得在linux或者win上面装jdk,里面有一个bin目录。它会去加载JRE的核心类库,如JRE目标下的rt.jar, charsets.jar等。
扩展类加载器(Extension ClassLoader)
负责加载java安装目录下的扩展目录 bin/ext 中jar类包
系统类加载器(Application ClassLoader)
负责加载ClassPath路径下的类包。可以理解成你写好的java代码,由这个类加载器加载到内存中去。
用户自定义加载器(User ClassLoader)
负责加载用户自定义路径下的类包
类加载机制
缓存机制:
所有加载过的Class都会被缓存,下次需要用到时直接去缓存区拿,拿不到的时候才去读取类的二进制数据并转成Class对象缓存起来。所以修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
全盘负责机制:
当一个类加载器负责一个类的加载时,该类所依赖的和引用的其他类也将由该类加载器负责载入,除非显示声明。
双亲委派机制:
下图是类加载器的父子关系,比如一个类需要加载,恰好子节点可以加载,但是它还是会去看父节点能否加载,同理父节点会一层一层的找上去。然后顶级父加载器会尝试加载,当父加载器无法加载的时候才一级一级往下分发,直到被加载成功为止。
双亲委派的优点:
1. 沙箱安全机制
2. 避免类的重复加载
比如jdk的java.lang包下有String类,如果我们在自己的程序里面也定义一个String类,这时候由于双亲委派机制,先让启动类加载器加载,此时jdk的String类就被加载成功了,而我们的String类由于和jdk的同名,将不会被系统类加载器加载。从安全上来看避免了有人恶意篡改系统代码。
现在我们知道了java文件会被编译成class文件,那么肯定class文件可以被编译成java文件,如果公司的代码被别人拿到了、只需要反编译过来就可以窃取公司源代码了。所以我们就可以考虑在编译时,用一些工具对字节码混淆;然后再类加载时,对加密的类采用自定义加载器来解密即可。