在引入类加载到JVM内存之前我们需要了解几个点:
-
什么样的文本内容算为Java类
符合Java语法、是多个特征集合的抽象,通过编译可以转换为字节码 -
Java类如何转变为字节码
通过Java编译器编为字节码,在这个过程会对语法进行检查。 在Intellj中只要启动main线程,所有已编写、保存的Java类无论与main线程是否相关都会被Intellj编译为字节码。
接着我们了解类【字节码】如何加载到JVM中
首先,我们需要一个加载器按照一定规则去把这件事做好,谁呢?类加载器。 类加载器一共分为四类:根类加载器[bootstrap ClassLoader],扩展类加载器[ExtClassLoader]、应用类加载器[application ClassLoader]以及自定义加载器。一定规则呢,规则就是一个类首先检查是否在应用加载器中被加载,如果没有则去扩展类加载器中检查,如果仍然没有就去根类加载器中检查,都没检查到则从BootStrapClassLoader尝试加载该类,如果不能够加载则继续向下一层加载器委托,而ApplicationClassLoader一定能够加载所有类。这样一种看似“鸡肋”的机制为什么要执行呢?原因在于保护第三方依赖类的安全性,设想如果编写人员不明所以编写了一个String类,而这个类其实有被作为第三方类引入,那我们该将哪一个String类加载到JVM内存中呢? 所以这套机制干嘛用到,就是用来防止这种错误的发生。自定义一个String类,类加载器会逐层检查某个加载器是否已有加载该类,如果该类确实已加载完毕则不需要重新加载一个类了,这样的话我们自定义的String类就得不到加载从而Java安全性能够得到保障,防止了编写人员的恶意污染。
话说回来,我们为什么需要四个类加载呢,那一定是每个类加载器都有自己的唯一作用呢!
-
根类加载器用来加载jrtJAVA-HOME/jre/lib/`rt.jar`、cesources.jar或sun.boot.class.path路径下的内容) ,用于提供JVM自身需要的类 -
扩展类加载器用来加载从JDK的安装目录的ire/lib/ext子目录 -
应用程序加载器,由启动类加载器加载。该类加载是程序中默认的类加载器, 一般来说, Java应用的类都是由它来完成加载
整个机制的实现集中体现在java.lang.ClassLoader的loadClass()方法之中
类的加载过程
当类加载对字节码加载完成后,接下里的就是字节码信息在JVM内存中一顿操作。为此,我们说当字节码加入到JVM内存时这个类就“活”了,而当这个类被卸载时这个类就“死”了,所谓“死”就可能是这个类不再被引用从而被GC回收机制回收内存,从“活”到“死”的这一个过程就是类在JVM中的生命周期。
生命周期分为加载---链接----初始化三个大步骤。
-
加载:这个过程和加载器联系很紧密 1. 通过全限定类名获取此类的二进制字节流、 2. 将字节流的静态存储结构转换为方法区的运行时数据结构 3. 在堆区中生成该类对应的单例Class对象,作为方法区中该类各种数据的访问入口 -
链接:又可以细分为验证、准备、解析三个小阶段 1. 验证是保证字节码不危害虚拟机,其信息也能够被虚拟机接受,有多重验证,如文件格式验证、元数据验证、字节码验证、符号引用验证 2. 准备为类变量分配内存并隐式设置类变量初始值,如int型为0,boolean型为false,这些变量所使用的内存都将在方法区中进行分配。 3. 解析虚拟机将常量池内的符号引用替换为直接引用的过程,像在常量池中一个变量的符号引用为info #91,这时并没有和在JVM中分配的内存直接映射,事实也是我们在编写代码时不可能就已将变量在内存中分配好,所以我们得将引用做一次替换,能够真正去映射到内存中。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。 -
初始化:前几个阶段都是一个为代码指令执行做铺垫的过程,通过前面一系列准备里我们这个台搭好只剩下去加载类中具体的信息,如加载类变量的显示赋值,静态代码块、静态内部类等,这些字面量全都存储再堆区。至此,我们对一个Class模板类信息的加载全部完成,我们接下来便可以通过这个模板对象去new、反射等操作。
接下来讨论的是一个类什么时候会启动在JVM中的加载。
- 启动main线程,对应的类会被加载
- 编写人员new一个对象、调用静态属性,则在字节码指令中执行new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令
- 动态生成类的实例化对象,反射得到Class模板对象通过模板对象的constructor().newInstance()
- 子类被加载时
/*
age isBurn未被显示赋值,所以在初始化那一阶段没它两的事
*/
Class<? extends Earth> aClass = new Earth().getClass();
Earth earth = aClass.getConstructor().newInstance();
System.out.println(earth);
public class Earth implements Cloneable{
public int age;
private String desc = "行星";
private boolean isBurn;
@Override
public String toString() {
return "Earth{" +
"age=" + age +
", desc='" + desc + ''' +
", isBurn=" + isBurn +
'}';
}
}
- TODO: Earth{age=0, desc='行星', isBurn=false}