首页 > 其他分享 >类的加载过程

类的加载过程

时间:2023-04-23 10:33:41浏览次数:36  
标签:初始化 public static println out 过程 class 加载


类的加载过程

类的加载指的是将类的.class文件中的二进制数据读入到内存中,然后在堆区创建一个这个类的java.lang.Class对象。

类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

一个java文件从被加载到被卸载这个生命过程,总共要经历7个阶段,JVM将类的生命周期过程分为:

类的加载过程_java

加载

加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中。

加载类的方式有以下几种:

  • 从本地系统直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载.class文件
  • 从专有数据库中提取.class文件
  • 将Java源文件动态编译为.class文件(服务器)

验证

验证是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

  • 文件格式验证:验证字节流是否符合Class文件格式的规范;例如jdk版本是否兼容,高版本编译的class无法在低版本运行。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,父类是否存在。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:确保解析动作能正确执行。

验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

准备

准备阶段是为类变量在方法区分配内存并设置默认值的阶段。对于该阶段有以下几点需要注意:

  1. 此时内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
  2. 此处设置的初始值是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
  3. 静态常量直接被赋予默认值。

先看下面一个例子:

package com.morris.jvm.load;

public class PrepareTest {

    public static int a = 10;

    public static final int b = 10;

}

在准备阶段a=0,而b=10。因为final修饰的常量(可直接计算出结果)不会导致类的初始化,是一种被动引用。更严谨的说法是常量在编译阶段会将其value生成一个ConstandValue,直接赋予10。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

初始化

初始化,为类的静态变量赋予正确的初始值,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

  1. 声明类变量是指定初始值
  2. 使用静态代码块为类变量指定初始值

初始化阶段最重要的一件事就是执行<cinit>方法(class initialize,类构造器),此方法由jvm编译后生成,使用javap命令查看class字节码可看到此方法。

  • 类构造器<cinit>:按源代码顺序收集类中所有静态代码块和类变量赋值语句组成。
  • 对象构造器<cinit>:按源代码顺序收集成员变量赋值和普通代码块,最后收集对象的构造方法,最终组成对象构造器。

注意:静态代码块中只能对后面的变量进行赋值,不能进行访问。

static {
        x = 20;
        System.out.println(x); // 此行编译不通过
    }
    
    public static int x = 10;

再来看一个例子,深刻理解类的初始化:

package com.morris.jvm.load;

public class BookTest {
    static BookTest book = new BookTest();
    static int amount = 112;

    static {
        System.out.println("书的静态代码块");
    }

    int price = 110;

    {
        System.out.println("书的普通代码块");
    }

    BookTest() {
        System.out.println("书的构造方法");
        System.out.println("price=" + price + ",amount=" + amount);
    }

    public static void main(String[] args) {
        staticFunction();
    }

    public static void staticFunction() {
        System.out.println("书的静态方法");
    }
}

运行结果如下:

书的普通代码块
书的构造方法
price=110,amount=0
书的静态代码块
书的静态方法

对于BookTest类,其类构造器可以简单表示如下:

static BookTest book = new BookTest();

    static {
        System.out.println("书的静态代码块");
    }

    static int amount = 112;

对于BookTest类,其对象构造器可以简单表示如下:

{
        System.out.println("书的普通代码块");
    }
    
    int price = 110;

    BookTest() {
        System.out.println("书的构造方法");
        System.out.println("price=" + price +",amount=" + amount);
    }
}

类的初始化顺序如下:

  1. 如果这个类还没有被加载和连接,那先进行加载和连接
  2. 假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
  3. 假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
  4. 总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法

类的主动使用与被动使用

每个类只有在首次主动使用才会被初始化。

以下是类的6种主动使用场景。

  1. 创建类的实例,也就是new一个对象
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(Class.forName)
  5. 初始化一个类的子类(会首先初始化子类的父类)
  6. 调用main方法
    除了以上6种情况外,其余的都称为被动使用,不会导致类的初始化。

关于类的主动使用与被动使用,下面是几个容易混淆的例子:

构造某个类的数组时不会导致类的初始化

package com.morris.jvm.load;

public class ArrayLoadTest {

    public static void main(String[] args) {
        A[] arrays = new A[10]; // // 只不过是在堆内存开辟了4byte*10的空间
    }

    public static class A {
        static {
            System.out.println("class A init.");
        }
    }
}

引用类的静态常量不会导致类的初始化

package com.morris.jvm.load;

import java.util.Random;

public class ConstantTest {

    public static void main(String[] args) {
        System.out.println(A.MAX);
        System.out.println(A.RANDOM);
    }

    public static class A {
        public static final int MAX = 10;  // 引用类的静态常量不会导致类的初始化

        public static final int RANDOM = new Random().nextInt(); // 只有在初始化后才能得到具体值,会导致了类的初始化

        static {
            System.out.println("class A init.");
        }
    }
}

运行结果如下:

10
class A init.
-859057135

从下面的字节码可以看出常量A.MAX在编译阶段会直接赋予10,而常量A.RANDOM需要初始化ConstantTest类后才能得出结果。

// access flags 0x19
  public final static I MAX = 10

  // access flags 0x19
  public final static I RANDOM

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 15 L0
    NEW java/util/Random
    DUP
    INVOKESPECIAL java/util/Random.<init> ()V
    INVOKEVIRTUAL java/util/Random.nextInt ()I
    PUTSTATIC com/morris/jvm/classloader/ConstantTest$A.RANDOM : I
   L1
    LINENUMBER 18 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "class A init."
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 19 L2
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
}

使用

当JVM完成初始化阶段之后,JVM便开始从入口方法开始执行用户的程序代码。

卸载

JVM规定了一个Class只有在满足一下三个条件的时候才会被GC回收,也就是类的卸载。

  1. 该类所有的实例已经把GC。
  2. 该类的Class对象不再被引用,即不可触及。
  3. 加载该类的ClassLoader已经被GC。

面试题

最后以一个面试题来结束本文。

package com.morris.jvm.load;

public class StaticTest {

    public static int k = 0;

    public static StaticTest t1 = new StaticTest("t1"); 

    public static StaticTest t2 = new StaticTest("t2"); 

    public static int i = print("i");

    public static int n = 99;

    public int j = print("j");
     
    {
        print("构造块");
    }

    static{
        print("静态块");
    }

    public StaticTest(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++n;
        ++i;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
        ++i;
        return ++n;
    }
    
    public static void main(String[] args) {
        new StaticTest("init");
    }
 
}

运行结果如下:

1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102

更多精彩内容关注本人公众号:架构师升级之路

类的加载过程_class_02


标签:初始化,public,static,println,out,过程,class,加载
From: https://blog.51cto.com/u_6784072/6216471

相关文章

  • Vulnhub之Harrison靶机详细测试过程(提权成功)
    Harrison作者:jasonhuawen靶机信息名称:SP:harrison地址:https://www.vulnhub.com/entry/sp-harrison,302/识别目标主机IP地址─(kali㉿kali)-[~/Vulnhub/Harrison]└─$sudonetdiscover-ieth1-r192.168.56.0/24Currentlyscanning:Finished!|ScreenView......
  • Vue设置默认加载页面,去掉地址栏#号
     {path:'/',component:Login,//想默认启动的页面},mode:"history"//去掉地址栏的#号 ......
  • 软件评测师--上班族备考过程记录
     考试目的:系统学习软件测试相关的知识通过软件评测师考试,拿到中级证书 报名时间:下半年7月底或8月底报名缴费:http://bm.ruankao.org.cn/sign/welcome有些地方不支持网上支付的,需要去当地单位缴费考试时间:2023/11/9考试规则:上午9点到11点半,基础知识,单选题(2B铅笔填涂答案,......
  • kubernetes1.27 kuberadm方式安装全过程
    配置hosts#master"10.102.4.13master">>/etc/hosts#node1"10.102.4.14node1">>/etc/hosts#node2"10.102.4.15node2">>/etc/hosts安装kubeadm、kubelet和kubectl(三台都要)cat<<EOF|sudotee/etc/yum.repos.......
  • 类加载与卸载
    加载过程  其中验证,准备,解析合称链接加载通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象.验证确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全.准备进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null).不包含final修......
  • 冰橙GPT提供开放接口 。提供与OPENAI官方一致的体验效果(同步返回数据,同时支持流式及非
    冰橙GPTchatGPT开放接口使用说明 【接入了腾讯云内容安全检测】冰橙GPT稳定提供API接口服务定时有人进行问题排查处理1小时内问题响应接入了腾讯云的内容安全检测有任何疑问请加入QQ交流群:310872519           1.请求地址:https://gpt.bcwhkj.cn/a......
  • OverTheWire攻关过程-Bandit模块2
    我们打开lv1-lv2查看信息机器翻译如下:下一级别的密码存储在名为的文件中-位于主目录中我们登陆查看下发现只有一个横杠cd不进入,cat查看不了原来是一个特殊字符使用cat./-可以查看文件内容知识点:这种方法有很多误解,因为使用-作为参数是指STDIN/STDOUT即dev/stdin或dev/stdou......
  • docker harbor安装过程
    环境准备安装最新版本docker-ce及docker-composemkdir-p/opt/harbor/certs 下载最新harbor合并有效签发证catpublic.crtchain.crt>www.crtharbor.yml配置文档 [root@repo_nzharbor]#catharbor.yml|grep-v^"#"|grep-v^".#"|grep-v^'#'|g......
  • SQL2000修改sa密码时提示【错误2812:未能找到储存过程’sp_passwoed’】的解决方法
    1.在用SQL2000数据库经常会遇见忘记sa密码,需要修改sa密码,但是有时候修改sa密码时会提示  错误2812:未能找到储存过程’sp_passwoed’2.遇到这种情况的解决方法是:打开开始菜单,找到SQLServer的程序组,选择运行程序组中的“查询分析器”,打开 3.打开“查询分析器”后会有一个......
  • Vulnhub之Healthcare靶机详细测试过程
    Healthcare作者:jasonhuawen靶机信息名称:地址:识别目标主机IP地址─(kali㉿kali)-[~/Vulnhub/Healthcare]└─$sudonetdiscover-ieth1-r192.168.56.0/24Currentlyscanning:192.168.56.0/24|ScreenView:UniqueHosts......