JVM核心机制 初始化与加载

JVM核心机制

- 类加载过程简述

  • JVMclass 文件加载到内存,并对数据进行校验,解析和初始化,最终形成 JVM 可以直接使用的 JAVA 类型的过程。

Java 类的二进制代码合并到 JVM 运行状态之中的过程

  • 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题。
  • 准备:正式为类变量 static 变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:虚拟机常量池内的符号引用替换为直接引用的过程。
  // 所谓常量池的符号引用 例如创建一个新的类:
  public class MyClass {
      public static void main(String[] args) {
         String str = "abv";
          int i = 5;
     }
  }
  // 这里面的 MyClass、str、i 都可以说是常量 存放于常量池中

初始化

  • 初始化阶段是执行类构造器 clinit() 方法的过程,类构造器 clinit() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块 static 块中的语句并合并产生的。
    clinit()方法平时我们是看不到的,而且也不能自己去定义它
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化、则需要先出发其父类的初始化。
  • 虚拟机会保证一个类的 clinit() 方法在多线程环境中被正确的加锁和同步。
    当一个类被初始化的时候肯定是线程安全的
  • 当访问一个 Java 类静态域的时候,只有真正声明这个域的类才会被初始化。
    在这里插入图片描述
    /**
    * Created by BF on 2017/9/14.
    * 了解JVM加载类全过程
    */
    public class demo01 {
        public static void main(String[] args) {
            // 当A对象被创建的时候 先会执行静态代码块,再实执行A的构造方法
            A a = new A();
            System.out.println(a.width);
            // 输出顺序为  创建初始化类A--> width = 300 --> 创建A对象
       }
    }
    class A {
        public static int width = 100;
        static {
            System.out.println("静态初始化类A");
            width = 300;
        }
        public A(){
            System.out.println("创建A对象");
        }
    }
    

加载

  • class 文件字节码内容加载到内存中,并将这些静态数据转换成方法 区中的运行时数据结构,在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区类数据的访问入口,这个过程需要类的加载器参与
  • 类加载全过程–>重点理解
    便于理解,先把代码贴出来
    public class demo01 {
        public static void main(String[] args) throws ClassNotFoundException {
            // 主动引用
            new A();
            System.out.println(A.width);
            Class.forName("com.wiceflow.JVM.A");
            // 被动引用
            System.out.println(A.MAX);
            A[] as = new A[10];
            System.out.println(B.width);
       }
    }
    class A extends A_Father{
        public static int width = 100;
        public static final int MAX = 200;
        static {
            System.out.println("静态初始化类A");
            width = 300;
        }
        public A(){
            System.out.println("创建A对象");
        }
    }
    class A_Father{
        static {
            System.out.println("静态初始化A的父类");
        }
    }
    class B extends A{
        static {
            System.out.println("静态初始化类B");
        }
    }
    
  • new A() 正常打印结果:
    静态初始化 A 的父类
    静态初始化类 A
    创建 A 对象
类的主动引用(一定会发生类的初始化)
  • new 一个类的对象

当 new 一个类的新对象,类必然会初始化 eg:new A()

  • 调用类的静态成员(除了 final 常量)和静态方法

eg:上述类A中有静态成员 width 当在其他类中调用到 A.width,类 A 一定会初始化

  • 使用 java.lang.reflect 包的方法对类进行反射调用

放射调用该类必会导致该类初始化,否则反射调用不会成功 eg: Class.forName("com.wiceflow.JVM.A")

  • 当虚拟机启动,java Hello,则一定会初始化 Hello 类,说白了就是启用 Main 方法所在的类
  • 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类

由上述代码可以看出 A 类继承 A_Father 类,当 A 类初始化的时候,因为其继承了 A_Father,所以会先初始化 A_Father 类,而每个类都会继承Object 类,所以这个类一定会被初始化

类的被动引用(不会发生类的初始化)
  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化
  • 通过子类引用父类的静态变量,不会导致子类初始化

例如上面类 B 继承了类 A,当 B 中调用 B.width 的时候,由于类 B 本身没有 width 变量,所以是取自其父类 A,这时候虚拟机初始化的是类 A,而类 B 并不会被初始化

  • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

eg: 在 A 中定义了一个 final 常量 MAX,这个常量在编译的时候就会创建并存储在方法区(特殊的堆)中,这时候调用只是在方法区将其取出,并不会涉及类 A 的初始化

此文是平时学习所记录的笔记,具体的类加载器下篇再做详细解释。

喜欢的可以关注我公众号:一只不想编码的程序员
更多精彩文章更新。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值