前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java类加载到类使用全过程

Java类加载到类使用全过程

作者头像
码上积木
发布2020-12-25 10:04:24
6960
发布2020-12-25 10:04:24
举报
文章被收录于专栏:码上积木码上积木
前言

上篇我们说到为了减少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函数,打印结果应该是什么?
代码语言:javascript
复制
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

拜拜

感谢大家的阅读,有一起学习的小伙伴可以关注下公众号—码上积木❤️ 每日三问知识点/面试题,积少成多。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上积木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类的生命周期
  • 类加载阶段
  • 类加载器种类
  • 类加载过程(双亲委派机制)
  • 类验证,准备,解析
  • 类初始化
  • 类实例化
  • 最后来一道经典的面试题
  • 参考
  • 拜拜
相关产品与服务
云硬盘
云硬盘(Cloud Block Storage,CBS)为您提供用于 CVM 的持久性数据块级存储服务。云硬盘中的数据自动地在可用区内以多副本冗余方式存储,避免数据的单点故障风险,提供高达99.9999999%的数据可靠性。同时提供多种类型及规格,满足稳定低延迟的存储性能要求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档