JVM--02--类加载与卸载

一、加载阶段

1.1 类加载器完成的操作(作用)

加载阶段,简言之,查找并加载类的二进制数据,生成Class的实例

在加载类时,Java虚拟机必须完成以下3件事情:

  1. 通过类的全名,获取类的二进制数据流
  2. 解析类的二进制数据流为方法区内的数据结构(Java类模型)
  3. 在堆中创建该类的Class类的对象,表示该类型。作为方法区这个类的各种数据的访问入口

(1)类加载图示如下:

(2)对象与Class对象的关系

1、通过该类的所有实例对象都可以得到这个Class对象;

2、通过Class对象我们也可以实例化对象。(反射)

1.2 类加载分类--显示加载、隐式加载

面试题三:反射中,Class.forName 和 ClassLoader 区别
1、java中class.forName()和classLoader都可用来对类进行加载。
2、class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,即执行类中的static块。

3、而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

1.3 类的加载器

  1. ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
  2. 类、类的加载器、类的实例之间为双向关联关系:

1.4 类加载器分类

  • JVM支持两种类型的加载器,分别为引导类加载器(BootStrap ClassLoader)自定义类加载器(User-Defined ClassLoader),所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
  • 数组类型的类加载器,与数组中元素的类加载器是一样的
  • 基本数据类型,是jvm预定义的,不需要加载器

1.5 双亲委派

双亲委派作用

  1. 避免类的重复加载
  2. 保护程序安全,防止核心API被随意篡改

保证程序安全性举例1:

使用IDE新建包java.lang,并在该包下新建String类,发现自定义的String类不会被加载

保证程序安全性举例2:

(1)将自定义的String类编译,包名还是:package java.lang;

(2)用解压缩软件打开rt.jar(不是解压),将自己的反编译文件拖进去替换JDK自带的String.class;

(3)如上操作后发现程序不能正常运营。

原因是虚拟机对一些核心API会进行安全检查:Hotspot had hardcoded assumptions about the field offsets of the various fields in java.lang.String. This means if you add fields to the String class which cause the class layouting logic to move the existing fields around you will break the JVM. 

具体的代码片段:

lambda/lambda/hotspot: 8f972594effc src/share/vm/classfile/javaClasses.cpp

String的校验见3210~3219行

二、链接阶段

2.1 验证

(1)目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。

(2)主要包括四种验证,文件格式验证,源数据验证,字节码验证,符号引用验证。

2.2 准备--为类变量(static修饰)指定默认初始值

注意点:

(1)final static修饰的基本数据类型类常量,在准备阶段直接显示赋值(final修饰的基本数据类型,编译后即确定了值)

(2)如果使用字符串字面量方式final static String修饰的类常量赋值,在准备环节也是直接显示赋值(final static String在编译后,也确定了值)

(3)其它final static修饰的引用类型,都是在链接--初始化阶段进行赋值。

(4)类加载阶段不会对成员变量赋值。(属性赋值是在new对象时才会初始化,即使是final修饰)

举例

public class LinkTest {

    private static long id=5L;//在准备阶段赋值为0L
    //age在准备阶段赋值为5L,解释:static final修饰的基本数据类型类变量,在准备阶段直接显示初始化
    private static final long age=5L;

    private static String str="abc";//在准备阶段赋值为null,初始化阶段才赋值为abc
    //str1在准备阶段赋值为abc,解释:使用字符串字面量方式为字符串常量赋值,在准备环节也是直接显示赋值
    private static final String str1="abc";
    //str2在准备阶段赋值为null,在链接--初始化阶段才赋值为abc
    private static final String str2=new String("abc");
    //dog在准备阶段赋值为null,在链接--初始化阶段才赋值为abc
    private static final Dog dog=new Dog(10);
}

2.3 解析

将常量池中的符号引用,转为运行时常量池中的对象

三、初始化--【静态属性赋值、静态代码块合并并顺序执行(注意此阶段无静态方法的执行)

初始化阶段就是执行类构造器方法clinit()的过程

(1)此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。 我们注意到如果没有静态变量赋值或静态代码块,那么字节码文件中就不会有clinit()方法

//举例:如下代码在编译后,并无clinit方法,原因见注释
public class LinkTest {
    private static final long age=5L;//在链接--准备阶段已完成赋值
    private static long num;//虽有类变量,但并无赋值
}

(2)<clinit>()方法中的指令按语句在源文件中出现的顺序执行

(3)<clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()

(4)若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕

(5)虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁

(6)初始化阶段,才开始java代码执行

四、类加载的线程安全性

类加载的过程是线程安全的,原因是加载过程中使用了synchronnized锁,源码如下:

    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) {
                //执行类加载
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

五、类卸载

如果程序运行过程中,堆中类的class对象不再被引用,方法区中的二进制数据会被卸载,称为类卸载。

启动类加载器加载的类型在整个运行期间是不可能被卸载的

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值