上篇我们说到为了减少Activity类加载的过程,所以可以预创建Activity。
有的朋友就问我,类加载,类实例化到底是怎样一个过程,为什么预加载一次就能减少下次加载的时间呢?今天就一起来回顾一下,这也是面试常考的点哦~
类的生命周期
借用网上的一张图
类从被加载到JVM
中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
类加载阶段
类的加载主要有三步:
- 将class文件字节码内容加载到内存中。
- 并将这些静态数据转换成方法区中的运行时数据结构。
- 在堆中生成一个代表这个类的
java.lang.Class
对象。
我们编写的java文件会在编译后变成.class文件
,类加载器就是负责加载class字节码文件,class文件在文件开头有特定的文件标识,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构。并且ClassLoader
只负责class文件的加载,至于它是否可以运行,则由执行引擎Execution Engine决定。
简单来说类加载机制就是从文件系统将一系列的 class 文件读入 JVM 内存中为后续程序运行提供资源的动作。
类加载器种类
类加载器种类主要有四种:
BootstrapClassLoader
:启动类加载器,使用C 实现ExtClassLoader
:扩展类加载器,使用Java实现AppClassLoader
:应用程序类加载器,加载当前应用的classpath的所有类UserDefinedClassLoader
:用户自定义类加载器
属于依次继承关系,也就是上一级是下一级的父加载器。
类加载过程(双亲委派机制)
类加载的过程可以用一句话概括:
先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区。
对于类加载器加载过程,就用到了双亲委派机制
,具体如下:
当一个类加载器收到了类加载的请求,它不会直接去加载这类,而是先把这个请求委派给父加载器去完成,依次会传递到最上级也就是启动类加载器,然后父加载器会检查是否已经加载过该类,如果没加载过,就会去加载,加载失败才会交给子加载器去加载,一直到最底层,如果都没办法能正确加载,则会跑出ClassNotFoundException
异常。
举例:
- 当
Application ClassLoader
收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。 - 当
Extension ClassLoader
收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。 - 如果
Bootstrap ClassLoader
加载失败(在<JAVA_HOME>lib
中未找到所需类),就会让Extension ClassLoader尝试加载。 - 如果
Extension ClassLoader
也加载失败,就会使用Application ClassLoader
加载。 - 如果
Application ClassLoader
也加载失败,就会使用自定义加载器去尝试加载。 - 如果均加载失败,就会抛出ClassNotFoundException异常。
这么设计的原因主要有两点:
- 这种层级关系可以避免类的重复加载。
- 是为了防止危险代码的植入,比如String类,如果在
AppClassLoader
就直接被加载,就相当于会被篡改了,所以都要经过老大,也就是BootstrapClassLoader
进行检查,已经加载过的类就不需要再去加载了。
代码看多了,都觉得文字是一种比较复杂的表达方式了,还是代码比较清晰明了,不知道你们有没有这样的感觉,直接上代码:
代码语言:javascript复制protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
synchronized (getClassLoadingLock(name)) {
// 检查类是否已经加载过,如果已经加载过,则直接返回
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 委派父类加载器去加载类
try {
if (parent != null) {
//一直找到最顶级的parent
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 委派父类加载器如果加载失败则调用findClass方法进行加载动作
if (c == null) {
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类验证,准备,解析
验证
。检查class是否符合要求,非必须阶段,对程序的运行期没有影响。准备
。给类中的静态变量分配内存空间,这时候的内存分配仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起在堆中进行分配。解析
。虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用理解为一个标示,而直接引用直接指向内存中的地址。
类初始化
这个类初始化和变量初始化可不一样,很多人会把这个弄混,认为初始化肯定是在实例化之后,其实不然。
类初始化
指的是完成程序执行前的准备工作,会执行执行类构造器<clinit>()
方法,在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。
初始化只在类加载的时候执行一次。
它的触发时机主要有以下几种情况:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时。
- 类被反射调用的时候。
- 初始化一个类的时候,如果其父类没有被初始化,会先初始化其父类。
类实例化
在类初始化完成之后,就可以进行类的实例化了。
类实例化
指的是创建一个对象的过程,这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。
即new Object()。
最后来一道经典的面试题
- 两个类,一个子类Son,一个父类Father,然后运行main函数,打印结果应该是什么?
public class Father {
private int i = test();
private static int j = method();
static {
System.out.println("(1)");
}
Father() {
System.out.println("(2)");
}
{
System.out.println("(3)");
}
public int test() {
System.out.println("(4)");
return 1;
}
public static int method() {
System.out.println("(5)");
return 1;
}
}
public class Son extends Father {
private int i = test();
private static int j = method();
static {
System.out.println("(6)");
}
Son() {
System.out.println("(7)");
}
{
System.out.println("(8)");
}
public int test() {
System.out.println("(9)");
return 1;
}
public static int method() {
System.out.println("(10)");
return 1;
}
}
代码语言:javascript复制 public static void main(String[] args) {
Son s1 = new Son();
System.out.println();
Son s2 = new Son();
}
答案明天见啦~
最后欢迎大家来码上积木微信讨论群
,想加群的在公众号首页点击菜单 “联系我—加讨论群”,有我的微信号,记得加我备注“讨论群”哦。
参考
https://segmentfault.com/a/1190000023876273 https://www.cnblogs.com/throwable/p/12272269.html https://www.cnblogs.com/pu20065226/p/12206463.html
拜拜
感谢大家的阅读,有一起学习的小伙伴可以关注下公众号—
码上积木
❤️ 每日三问知识点/面试题,积少成多。