Java #类的加载过程 #类加载子系统

类加载子系统

类的加载阶段可以分为三个阶段,这三个阶段为加载阶段、链接阶段、初始化阶段。

阶段一(加载阶段 Loading):

  1. 通过一个类的获取定义此类的二进制字节流
  2. 转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的  java.lang.Class 对象,作为该类的访问入口

阶段二(链接阶段 Linking):

  1. 验证:目的在于确保Class文件的字节流包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
  2. 准备:为类变量分配内存并且设置该类变量的默认初始值,即零值。

注:在链接阶段如果变量被final修饰,则直接被赋值

private static int a = 1;
private static final int b = 2;

小编用代码和字节码文件来说明一下: 

 查看字节码文件,由于<clinit>是初始化阶段发生的(也就是在阶段三),只能看到a被赋值为1,说明b是在链接阶段被赋值的(在阶段三中看不到b被赋值),所以证明final是在链接阶段发生的

(这里在IDEA中装上jclasslib插件就可以看到字节码文件了,View-Show Bytecode With Jclasslib就可以看到当前类的字节码文件啦)

阶段三(初始化阶段 Initialization):

  1. 初始化阶段就是执行类构造器方法 <clinit>()的过程。该方法体是编译器自动收集类中的所有类变量(静态成员)的赋值动作和静态代码块中的语句合并而来(简单的说:<clinit>()为static的动作的合并语句),<clinit>() 执行前,父类的<clinit>() 已经执行完毕。<clinit>() 方法在多线程下被同步加锁。

例:说明一下<clinit>()

在<clinit>()里可以看到,num -->1-->2,number -->20 -->10,按照顺序执行,除此之外,我们还发现声明可以放在后面,但是调用变量必须在声明后调用

 为了说明<clinit>是被同步加锁的,来看下面的代码的执行结果:

public class ClinitThreadTest {
    public static void main(String[] args) {
        Thread t1 = new Thread("线程1"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始");
                C c = new C();
                System.out.println(Thread.currentThread().getName() + "结束");
            }
        };
        Thread t2 = new Thread("线程2"){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始");
                C c = new C();
                System.out.println(Thread.currentThread().getName() + "结束");
            }
        };
        t1.start();
        t2.start();
    }
}
class C{
    static {
        System.out.println(Thread.currentThread().getName() + "<clinit>()初始化类C");
        try {
            Thread.currentThread().sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果为: 

线程1开始
线程2开始
线程2<clinit>()初始化类C
线程2结束
线程1结束

解释一下代码:

创建了两个线程1、2,线程2进入到类C的static代码块时也就是执行<clinit>()方法,执行输出sysout语句,线程进入睡眠,sleep()不会释放锁,我们看到线程1没有进入<clinit>方法,说明<clinit>()方法被加锁了,而且等线程2将<clinit>()执行完后,线程1没有执行,说明线程1已经知道C的<clinit>方法被执行过了,由此也说明<clinit>不但被加锁,而且是被同步加锁的。(到这里就更加理解了:wow原来静态成员和代码块只能被加载一次是这么回事)

经过了类加载子系统后,将它们加载进了运行时数据区

小编来配一张图,更直观:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值