0
点赞
收藏
分享

微信扫一扫

趣笔阁爬虫实验

东林梁 2024-09-26 阅读 38

类加载

类生命周期

 类的生命周期包括以下 7 个阶段:
        加载(Loading)
        验证(Verification)
        准备(Preparation)
        解析(Resolution)
        初始化(Initialization)
       
 使用(Using)
        卸载(Unloading)

结束类生命周期的几种场景:
        执行 System.exit()方法
        程序正常执行结束
        程序执行中遇到了异常或错误而异常终止
        操作系统出现错误或强制结束程序而导致 JVM虚拟机进程终止

类加载过程

        类加载过程包含:加载、验证、准备、解析、初始化,一共包括 5 个阶段。

加载

验证

准备

  • 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是元空间区的内存。
  • 实例变量不会在这阶段分配内存,它会在对象实例化时,随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
  • 初始值一般为 0 值。

例一:

//比如下面这个 static 修饰的变量,在准备这一步,是给他分配空间,并赋予系统高要求的零值
//                              在初始化时才给a 赋予我们要求的初始化值3
public static int a = 3;

 例二:

//下面的 a 是我们定义的静态常量,它则被初始化为 3 而不是 0 
public static final int a = 3;

解析

初始化

        例如:以下代码中静态变量 i 只能赋值,不能访问,因为i 定义在静态代码块
的后面。

public test {
    static{
        i = 3;                    //给变量赋值可以正常编译通过
        System .out.println(i);   //这句编译器会提示“非法向前引用”
    }
    static int i = 1;
}

         由于父类的 <clinit>()方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:

static class Parent{
    Public static int A = 1;
    static {
        A = 2;
    }
}

static class Son ectends Parent{
    public static int B = A;
}

    public static void main(String[] args){
        system.out.println(Son.B); // 2
    }

<clinit>线程安全
        虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>()方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中,该阻塞非常隐蔽,几乎不会被察觉。

类的加载时机

主动引用

      虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了只有下列六种情况必须对类进行加载:

  • 当遇到 new、getstatic、putstatic、或 invokestatic 这4条字节码指令时,比如 new 一个对象,读取一个静态字段(未被final修饰)、或调用一个类的静态方法时。        

                当 jvm 执行 new 指令时会加载类。即:当程序创建一个类的实例对象。

                当 jvm 执行 getstatic 指令时会加载类。即:程序访问类的静态变量(不是静态常量,常量会

         被加载到运行时常量池)。

                当 jvm 执行 putstatic 指令时会加载类。即:程序给类的静态变量赋值。

                当 jvm 执行 invokestatic 指令时会加载类。即:程序调用类的静态方法。

  • 使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forname("."),或 newInstance()等等。如果类没初始化,需要触发类的加载。
  • 加载一个类,如果其父类还未加载,则先触发该父类的加载。
  • 当虚拟机启动时,用户需要定义一个要执行的主类(包含 main()方法的类),虚拟机会先加载这个类。
  • 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了加载,则该接口要在实现类之前被加载。 

被动引用

        除主动引用之外,所有引用类的方式都不会触发加载成为被动引用。

被动引用的常见例子包括:

System.out.println(Subclass.value); // value 字段在 SubClass类的父类中定义
SuperClass [] sca = new SuperClass[10];

System.out.println(ConstClass.HELLOWORLD);

类加载器

类加载器

类加载器分类

public class ClassLoaderTest {
    public static void main(String[] args) {
        //(启动类)系统类加载器:
        ClassLoader systemClassLoader = ClassLoader. getSystemclassLoader();
        System.out.println(systemClassLoader); //sun.misc.Launcher$AppClassLoader@73d16e93

        //扩展类加载器:
        ClassLoader extendClassLoader = systemClassLoader. getParent();
        System.out.println(extendClassLoader); //sun.misc.Launcher$ExtClassLoader@15db9742

        // 引导类加载器:
        ClassLoader bootstrapClassLoader = extendClassLoader. getParent();
        System.out.println(bootstrapClassLoader); // null

        //用户自定义的类默认用系统类加载器
        ClassLoader classLoader = ClassLoaderTest. class. getClassLoader();
        System.out.println(classLoader); // sun.misc.Launcher$AppClassLoader@73d16e93
    }
}

自定义类加载器使用场景

双亲委派模型

 双亲委派机制工作原理

双亲委派的作用

         例如: java.lang.Object 存放在 rt.jar 中,如果编写另外一个  java.lang.Object 并放到ClassPath中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的Object比在ClassPath中的Object优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中使用的所有的 Object都是由启动类加载器所加载的 Object。

双亲委派额实现源码

        以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出ClassNotFoundException,此时尝试自己去加载。

public abstract class classLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    public Class <? > loadClass(String name) throws ClassNotFoundException {
        return loadclass(name, false);

    }

    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) {
                try {
                    if (parent != null) {
                        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
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class <? > findclass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

SPI打破双亲委派

对象的创建过程

类加载检查

分配内存

初始化零值

设置对象头

执行 init 构造方法

举报

相关推荐

0 条评论