JAVA应用】JVM加载类的步骤是什么?

2021-05-11 08:52发布

22条回答
放学别走
1楼 · 2021-05-11 09:16.采纳回答

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。



类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

加载.class文件的方式

  • 从本地系统中直接加载

  • 通过网络下载.class文件

  • 从zip,jar等归档文件中加载.class文件

  • 从专有数据库中提取.class文件

  • 将Java源文件动态编译为.class文件

不吃鱼的猫
2楼 · 2021-05-11 09:19

先把类变成.class字节码文件,当需要用到的时候后在通过反编译回来

lucky璐呀
3楼 · 2021-05-11 09:36

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

20200921文 - 做更棒的自己!
4楼 · 2021-05-11 10:00

类从加载到虚拟机到卸载,它的整个生命周期包括:加载(Loading),验证(Validation),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading)。其中,验证、准备和解析部分被称为连接(Linking)。

加载:
在加载阶段,虚拟机主要完成三件事:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构。
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区域数据的访问入口。
验证:
验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害。如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。验证过程分为四个阶段:
1.文件格式验证:验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理。
2.元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范。
3.字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。
4.符号引用验证:符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。
准备:
准备阶段为变量分配内存并设置类变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量),而不包括类的实例变量。对已非final的变量,JVM会将其设置成“零值”,而不是其赋值语句的值:
pirvate static int size = 12;
那么在这个阶段,size的值为0,而不是12。 final修饰的类变量将会赋值成真实的值。
解析:
解析过程是将常量池内的符号引用替换成直接引用。主要包括四种类型引用的解析。类或接口的解析、字段解析、方法解析、接口方法解析。
初始化:
在准备阶段,类变量已经经过一次初始化了,在这个阶段,则是根据程序员通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,父类的初始化等。
至于使用和卸载阶段阶段,这里不再过多说明,使用过程就是根据程序定义的行为执行,卸载由GC完成。

小磊子
5楼 · 2021-05-11 10:36

1. JVM类加载过程

    1.概述


    从类的生命周期而言,一个类包括如下阶段:




    


        加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。


    2. 类加载时机


    加载(loading)阶段,java虚拟机规范中没有进行约束,但初始化阶段,java虚拟机严格规定了有且只有如下5种情况必须立即进行初始化(初始化前,必须经过加载、验证、准备阶段):


    (1)使用new实例化对象时,读取和设置类的静态变量、静态非字面值常量(静态字面值常量除外)时,调用静态方法时。


    (2)对内进行反射调用时。


    (3)当初始化一个类时,如果父类没有进行初始化,需要先初始化父类。


    (4)启动程序所使用的main方法所在类


    (5)当使用1.7的动态语音支持时。


    如上5种场景又被称为主动引用,除此之外的引用称为被动引用,被动引用有如下3种常见情况


通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

定义对象数组和集合,不会触发该类的初始化

类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)

public class TestClass {

    public static void main(String[] args) {

        System.out.println(ClassInit.str);

        System.out.println(ClassInit.id);

    }

}

class ClassInit{

    public static final long id=IdGenerator.getIdWorker().nextId();//需要初始化ClassInit类

    public static final String str="abc";//字面值常量

    static{

        System.out.println("ClassInit init");

    }

}

通过类名获取Class对象,不会触发类的初始化。如System.out.println(Person.class);

通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化。

通过ClassLoader默认的loadClass方法,也不会触发初始化动作

    注意:被动引用不会导致类初始化,但不代表类不会经历加载、验证、准备阶段。


    3. 类加载方式


    这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。


   (1)隐式加载


创建类对象

使用类的静态域

创建子类对象

使用子类的静态域

在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class

在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class

在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件

    (2)显式加载


ClassLoader.loadClass(className),只加载和连接、不会进行初始化

Class.forName(String name, boolean initialize,ClassLoader loader); 使用loader进行加载和连接,根据参数initialize决定是否初始化。

2. 加载阶段

    加载是类加载过程中的一个阶段,不要将这2个概念混淆了。


    在加载阶段,虚拟机需要完成以下3件事情:


通过一个类的全限定名来获取定义此类的二进制字节流

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

    加载.class文件的方式


从本地系统中直接加载

通过网络下载.class文件

从zip,jar等归档文件中加载.class文件

从专有数据库中提取.class文件

将Java源文件动态编译为.class文件    

    相对于类生命周期的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。


3. 连接阶段

  3.1 验证:确保被加载的类的正确性


    确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。


文件格式验证:验证字节流是否符合Class文件格式的规范,如:是否以模数0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围内等等。

元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;如:这个类是否有父类,是否实现了父类的抽象方法,是否重写了父类的final方法,是否继承了被final修饰的类等等。

字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如:操作数栈的数据类型与指令代码序列能配合工作,保证方法中的类型转换有效等等。

符号引用验证:确保解析动作能正确执行;如:通过符合引用能找到对应的类和方法,符号引用中类、属性、方法的访问性是否能被当前类访问等等。

    验证阶段是非常重要的,但不是必须的。可以采用-Xverify:none参数来关闭大部分的类验证措施。


  3.2 准备:为类的静态变量分配内存,并将其赋默认值


    为类变量分配内存并设置类变量初始值,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:


只对static修饰的静态变量进行内存分配、赋默认值(如0、0L、null、false等)。

对final的静态字面值常量直接赋初值(赋初值不是赋默认值,如果不是字面值静态常量,那么会和静态变量一样赋默认值)。

  3.3 解析:将常量池中的符号引用替换为直接引用(内存地址)的过程


    符号引用就是一组符号来描述目标,可以是任何字面量。属于编译原理方面的概念如:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。


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


    假设:一个类有一个静态变量,该静态变量是一个自定义的类型,那么经过解析后,该静态变量将是一个指针,指向该类在方法区的内存地址。 具体见后续文章。


4. 初始化:为类的静态变量赋初值

    赋初值两种方式:


定义静态变量时指定初始值。如 private static String x="123";

在静态代码块里为静态变量赋值。如 static{ x="123"; } 

    注意:只有对类的主动使用才会导致类的初始化。


5. clinit 与 init

    在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init。


  5.1 clinit 


    clinit指的是类构造器,主要作用是在类加载过程中的初始化阶段进行执行,执行内容包括静态变量初始化和静态块的执行。


    注意事项:


    1. 如果类中没有静态变量或静态代码块,那么clinit方法将不会被生成。


    2. 在执行clinit方法时,必须先执行父类的clinit方法。


    3. clinit方法只执行一次。


    3. static变量的赋值操作和静态代码块的合并顺序由源文件中出现的顺序决定。如下代码所示:


public class TestClass {

    public static void main(String[] args) {

        ClassInit init=ClassInit.newInstance();

 

        System.out.println(init.x);

        System.out.println(init.y);

    }

}

 

class ClassInit{

    private static ClassInit init=new ClassInit();

    public static int x;

    public static int y=0;

    static{

        x=10;

        y=10;

    }

    private ClassInit(){

        x++;

        y++;

    }

    public static ClassInit newInstance(){

        return init;

    }

}

//在类加载到连接完成阶段,ClassInit类在内存中的状态为:init=null,x=0,y=0

//初始化阶段时,需要执行clinit方法,该方法类似如下伪代码:

clinit(){

//init=new ClassInit();调用构造方法

    x++;//x=1 因为此时x的值为连接的准备阶段赋的默认值0,然后++变成1

    y++;//y=1 因为此时y的值为连接的准备阶段赋的默认值0,然后++变成1

    //x=0;//为什么这里没有执行x=0,因为程序没有给x赋初值,因此在初始化阶段时,不会执行赋初值操作

    y=0;//因为类变量y在定义时,指定了初值,尽管初值为0,因此在初始化阶段的时候,需要执行赋初值操作

    x++;//第一个静态块的自增操作,结果为x=2;

    y++;//第一个静态块的自增操作,结果为y=1;

}

//所以最终结果为x=2,y=1

//如果private static ClassInit init=new ClassInit(); 代码在public static int y=0;后面,那么clinit方法的伪代码如下:

clinit(){

    //x=0;//这里虽然没有执行,但此时x的值为连接的准备阶段赋的默认值0

    y=0;//因为类变量y在定义时,指定了初值,尽管初值为0,因此在初始化阶段的时候,需要执行赋初值操作

//init=new ClassInit();调用构造方法

    x++;//x=1 因为此时x的值为连接的准备阶段赋的默认值0,然后++变成1

    y++;//y=1 因为此时y的值为初始化阶段赋的初值,只是这个初值刚好等于默认值0而已,然后++变成1

    x++;//第一个静态块的自增操作,结果为x=2;

    y++;//第一个静态块的自增操作,结果为y=2;

}

//最终结果为x=2,y=2

    5.2 init


    init指的是实例构造器,主要作用是在类实例化过程中执行,执行内容包括成员变量初始化和代码块的执行。


    注意事项:


    1. 如果类中没有成员变量和代码块,那么clinit方法将不会被生成。


    2. 在执行init方法时,必须先执行父类的init方法。


    3. init方法每实例化一次就会执行一次。


    3. init方法先为实例变量分配内存空间,再执行赋默认值,然后根据源码中的顺序执行赋初值或代码块。如下代码所示:


public class TestClass {

    public static void main(String[] args) {

        ClassInit init=new ClassInit();

    }

}

 

class ClassInit{

    public int x;

    public int y=111;

    public ClassInit(){

        x=1;

        y=1;

    }

    {

        x=2;

        y=2;

    }

    {

        x=3;

        y=3;

    }

}

//实例化步骤为:先为属性分配空间,再执行赋默认值,然后按照顺序执行代码块或赋初始值,最后执行构造方法

//根据上述代码,init方法的伪代码如下:

init(){

x=0;//赋默认值

    y=0;//赋默认值

    y=111;//赋初值

    x=2;//从上到下执行第一个代码块

    y=2;//从上到下执行第一个代码块

    x=3;//从上到下执行第二个代码块

    y=3;//从上到下执行第二个代码块

    //ClassInit();执行构造方法

    x=1;//最后执行构造方法

    y=1;//最后执行构造方法

}

//如果上述代码的成员变量x,y的定义在类最后时,那么init方法的伪代码如下:

init(){

x=0;//赋默认值

    y=0;//赋默认值

    x=2;//从上到下执行第一个代码块

    y=2;//从上到下执行第一个代码块

    x=3;//从上到下执行第二个代码块

    y=3;//从上到下执行第二个代码块

    y=111;//赋初值

    //ClassInit();执行构造方法

    x=1;//最后执行构造方法

    y=1;//最后执行构造方法

}

6. 卸载阶段

    执行了System.exit()方法


    程序正常执行结束


    程序在执行过程中遇到了异常或错误而异常终止


    由于操作系统出现错误而导致Java虚拟机进程终止



是开心果呀 - 热爱生活
6楼 · 2021-05-11 10:42

从类的生命周期而言,一个类包括如下阶段:

    

        加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序进行,而解析阶段则不一定,它在某些情况下可能在初始化阶段后在开始,因为java支持运行时绑定。


加载:这个很简单,程序运行之前jvm会把编译完成的.class二进制文件加载到内存,供程序使用,用到的就是类加载器classLoader ,这里也可以看出java程序的运行并不是直接依   靠底层的操作系统,而是基于jvm虚拟机。如果没有类加载器,java文件就只是磁盘中的一个普通文件。

2、连接:连接是很重要的一步,过程比较复杂,分为三步  验证  》准备  》解析    

  验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件 

  准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 

  解析:把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用


三岁奶猫
8楼 · 2021-05-11 14:23

类加载过程:加载、验证、准备、解析、初始化

flame
9楼 · 2021-05-11 15:19

步骤1:加载

示意图


步骤2:验证

示意图


步骤3:准备

示意图


步骤4:解析

示意图


步骤5:初始化



总结

  • 本文全面讲解类加载过程的5个步骤,总结如下

相关问题推荐

  • 回答 20

    100-199 用于指定客户端应相应的某些动作。 200-299 用于表示请求成功。 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。 400-499 用于指出客户端的错误。 400 语义有误,当前请求无法被服务器理解。 401 当前请求需要用户验证...

  • 回答 5
    已采纳

    1、相同点(1)都是表现层框架,都是基于MVC设计模型(2)底层都离不开 Servlet API(3)处理请求的机制都是一个核心控制器2、不同点(1)SpringMVC的入口是Servlet,而Struts2的入口是Filter(2)SpringMVC是基于方法设计的,而Struts2是基于类(3)SpringMV...

  • 回答 23
    已采纳

    (1)idea启动时会有两个快捷方式,安装完后默认生成在桌面的是32位的idea的快捷方式,如果我们使用这个快捷方式运行大项目,一般都会很卡。解决方法是找到idea的安装目录,然后进入bin文件夹,找到名称为idea64的应用程序,右键他生成桌面快捷方式。以后每次...

  • 回答 12
    已采纳

    获取Map集合中所有的key可以通过map集合的keySet()方法获取例如:    Map map = new HashMap();    map.put(xx,xx); //存放数据    //.... 省略    Set set = map.keySet();    //可以通过迭代器进行测试    Iterator iter = set.iter...

  • 回答 4
    已采纳

    Java中有八种数据类型,基础数据类型分别是:byte,short,int,long,float,double,char,boolean,引用数据类型分别是:数组,类和接口。方法传参的时候我们有两种,一种是形式参数(定义方法时写的参数),一种是实际参数(调用方法时给的具体值)。首先...

  • 回答 15
    已采纳

    现在的架构很多,各种各样的,如高并发架构、异地多活架构、容器化架构、微服务架构、高可用架构、弹性化架构等,还有和这些架构相关的管理型的技术方法,如 DevOps、应用监控、自动化运维、SOA 服务治理、去 IOE 等等,还有很多。分布式架构其实就是分布式系...

  • 回答 10

    1、监控GC的状态使用各种JVM工具,查看当前日志,分析JVM参数的设置,分析堆内存快照和GC日志,根据实际的各区域的内存划分和GC的执行时间,判断是否需要进行优化2、分析结果、判断是否需要优化如果各项参数设置合理,系统没有超时的日志出现,GC频率也不高,...

  • 回答 9
    已采纳

    从两个方面对ElasticSearch和Solr进行对比,从关系型数据库中的导入速度和模糊查询的速度。单机对比1. Solr 发布了4.0-alpha,试了一下,发现需要自己修改schema,好处是它自带一个data importer。在自己的计算机上测试了一下,导入的性能大概是:14分钟导入 ...

  • 回答 10
    已采纳

    操作系统中有若干进程并发执行,它们不断申请、使用、释放系统资源,虽然系统的进 程协调、通信机构会对它们进行控制,但也可能出现若干进程都相互等待对方释放资源才能 继续运行,否则就阻塞的情况。此时,若不借助外界因素,谁也不能释放资源,谁也不能解 ...

  • 回答 6

    MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以...

  • 回答 6

    学vue应该要先学习javascript 的基础知识和用法。

  • 回答 7

    synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 2. 修饰一个方法,被修饰的方法称为同步方法,其作用...

  • 回答 9

    是一个文件服务器,用来上传下载文件.它是一个分布式集群的.分2个角色调度和存储.上传时配置调度后上传.用的时候需要搭建.如果不会搭建想省事还可以选择阿里的oss .项目中应用还是比较多的因为一般项目都有文件上传和下载.选择目前就这2种方案.根据公司情况选...

  • 回答 8

    使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。...

  • 回答 6

    一、装箱和拆箱原始类型转换为对象类型就是装箱,反之就是拆箱。原始类型byte,short,char,int,long,float,double,boolean对应的封装类为Byte,Shor,Character,Integer,Long,Float,Double,Boolean.二、源码解读自动装箱时编译器调用valueOf将原始类型值转换成对...

没有解决我的问题,去提问