一.class加载过程
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验/准备/解析和初始化,最终
形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
loading → linking (verification-> preparation → resolution )-> initializing
loading:把class文件load到内存中,采用双亲委派,主要是为了安全性
verification:校验class文件是否符合标准
preparation:静态变量分配内存并设初始值的阶段(不包括实例变量)
resolution:把符号引用转换为直接引用
initializing:静态变量赋初始值
遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始
化,则需要先触发其初始化阶段。
能够生成这四条指令的典型Java代码场景有:
1.使用new关键字实例化对象。
2.读取或设置一个类型的静态字段。(被final修饰、已在编译期把结果放入常量池的静态字段除外,但是
final static int i=10;如果没有=10还是会在准备阶段给它赋默认值,在初始化的时候赋值。)
3.调用一个类型的静态方法。
4.使用java.lang.reflect包的方法对类型进行反射调用。
5.虚拟机启动的时候被执行的主类必须初始化。
6.初始化子类时,父类首先初始化。
二.类加载器
类加载器实现了loading这个动作,把class文件加载到内存。
1.启动类加载器(BootstrapClassLoader):负责加载存放在lib/rt.jar charset.jar 等目录下的核心类,由c++实现。启动类加载器无法被Java程序直接引用(classLoader的loadClass方法中,若parent为 null则使用启动类加载器。
2.扩展类加载器(ExtensionClassLoader):负责加载lib/ext目录下的类,在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现 。
3.应用程序类加载器(ApplicationClassLoader):负责加载用户类路径(ClassPath)下所有的类库。由sun.misc.Launcher$AppClassLoader实现 ,是ClassLoader类中的getSystemClassLoader()方法的返回值。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这 个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载 请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求 (它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
源码实现:
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) {//调用父亲(不为BootstrapClassloader)去查缓存
c = parent.loadClass(name, false);
} else {//父亲为BootstrapClassloader
c = findBootstrapClassOrNull(name);//查缓存和加载/lib/rt/下的核心类
}
} 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;
}
}
图解如下:
三.自定义类加载器
- 继承ClassLoader抽象类 extends ClassLoader
2.重写findClass() 方法,读取class文件,并调用defineClass(),override findClass() → defineClass(byte[] b, int off, int len)
3.加密(可以对class字节码文件进行加密处理,可选)
源码实现如下:
package com.tc.javabase.jvm;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* @Classname MyClassLoader
* @Description 自定义类加载器
*
* 1. 继承ClassLoader抽象类 extends ClassLoader
* 2.重写findClass() 方法,读取class文件,并调用defineClass(),override findClass() -> defineClass(byte[] b, int off, int len)
* 3.加密(可以对class字节码文件进行加密处理,可选)
* @Date 2020/7/29 00:17
* @Created by zhangtianci
*/
public class MyClassLoader extends ClassLoader{
public static int seed = 0B10110110;
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace('.', '/').concat(".ztcclass"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b ^ seed);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}
public static void main(String[] args) throws Exception {
encFile("com.tc.javabase.jvm.Hello");
ClassLoader l = new MyClassLoader();
Class clazz = l.loadClass("com.tc.javabase.jvm.Hello");
Hello h = (Hello)clazz.newInstance();
h.sayHello();
System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
}
private static void encFile(String name) throws Exception {
File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/", name.replace(".", "/").concat(".ztcclass")));
int b = 0;
while((b = fis.read()) != -1) {
fos.write(b ^ seed);
}
fis.close();
fos.close();
}
}
四.破坏双亲委派模型
1.如何破坏双亲委派模型?
通过重写loadClass()方法。
2.什么时候破坏过双亲委派模型?
- JDK1.2之前,自定义classLoader都必须重写loadClass ()方法。(缺陷)
- ThreadContextClassLoader可以实现基础类调用实现类的代码
- 热启动/热部署 tomcat 都有自己的模块指定classLoader(可以加载同一类库的不同版本)
破坏双亲委派模型的简单实现:
/**
* @Classname MyClassLoader3
* @Description 自定义类加载机制 打破双亲委派机制(重写classLoad() )
*
* 当重新new 一个自定义加载器实例时 可以重新加载目标class文件到内存中 有一个新的目标class对象
* * 否则 内存中只会加载一次
* *
* * <----重写loadClass() 打破双亲委派机制时----->
* * 当同一个自定义加载器实例 想要加载目标class文件到内存两次(调用defineClass()方法) 会报错
* @Date 2020/7/29 17:27
* @Created by zhangtianci
*/
public class MyClassLoader3 extends ClassLoader{
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
File f = new File("/Users/zhangtianci/work/personal-projects/javabase/src/main/java/" + name.replace(".", "/").concat(".class"));
if(!f.exists()) return super.loadClass(name);
try {
InputStream is = new FileInputStream(f);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
五.混合模式
1.解释器: bytecode intepreter
2.jit:just in-time compiler
3.混合模式:
- 混合使用解释器+热点代码编译
- 起始阶段采用解释执行
- 热点代码检测 -多次被调用的代码 - 多次被调用的循环 -进行编译
- -Xmixd :默认为混合模式,启动速度较快,对热点代码实行检测和编译; -Xint:使用纯解释模式,启动很快,执行稍慢 ; -Xcomp: 使用纯编译模式,执行很快,启动稍慢。