1. 类加载实例化过程
- 当我们编写完一个*.java类后。
- 编译器(如javac)会将其转化为字节码。
- 转化的字节码存储在.class后缀的文件中(.class 二进制文件)。
- 接下来在类的加载过程中虚拟机JVM利用ClassLoader读取该.class文件将其中的字节码加载到内存中。
- 基于这些字节码信息创建相应的Class Object.
- 每个类在JVM中只加载一次,故而每个类都对应着一个唯一的Class对象(Class Object).
- 这个对象包含了类的元信息(metadata),如类的名称、包名、父类、实现的接口、字段、方法、构造函数等。
- class Objcect可以通过获取构造函数创建实例化的对象。
2. 类加载存储位置
- 字节码文件:
是Java源代码编译后的产物,它们以二进制的形式存储在文件系统中。 所以它并不存放于JVM的结构中。 - Class Object:
Class
对象被存储在JVM的方法区(Method Area)中的元空间(Metaspace)里。 - 实例化对象:
存放于堆内存(Heap Memory)
3. JVM利用classloader进行类加载和类初始化的区别
在Java中,类加载(Class Loading)和类初始化(Class Initialization)是两个不同的阶段,它们在类生命周期中扮演着不同的角色。下面是这两个阶段的区别:
- 类加载(Loading):
通过类的全名获取类的二进制数据。将这些数据转换成方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为这个类的各种数据的入口。然而,类的初始化(即执行静态初始化代码和静态初始化块)是在类加载的最后阶段(初始化阶段)由 JVM 自行完成的。所以我们在使用类反射,如User.class 获取class对象时,它是不会去执行静态代码块的。 - 类初始化(Class Initialization)
类初始化,广义上将它类加载过程中的最后一步,只不过这一步是动态的,而不是由程序启动之初就加载的,只有当类被主动使用时才会发生。这个阶段会执行类的静态初始化代码(在声明类变量时指定的初始值和在静态代码块中指定的代码)。这个过程只会在JVM首次主动使用类时发生一次。
4. 结合第三点,补充说明为什么反射中Object.class 不加载静态代码块,而Class.forName("ClassName")会加载静态代码块
Object.class 和 Class.forName("ClassName") 在触发类加载和初始化方面有不同的行为,这主要是因为它们使用的方式和上下文不同。
Object.class:
当你使用 Object.class 时,你实际上是在引用Java的根类Object的Class对象。由于Object类是Java运行时系统的一部分,并且它几乎总是在你开始执行任何Java代码之前就已经被加载和初始化了(因为其他所有的类都直接或间接地继承自Object类),所以当你引用Object.class时,它不会再次触发Object类的加载和初始化。 静态代码块只会在类首次被主动使用时执行,而由于Object类几乎总是已经在使用之前就被加载和初始化了,因此通过Object.class引用它不会再次触发静态代码块的执行。
Class.forName("ClassName"):
Class.forName("ClassName") 是一个用于动态加载类并触发其初始化的方法。当你调用这个方法并传入一个类名(作为字符串)时,JVM会尝试找到并加载这个类。如果类还没有被加载和初始化,JVM会首先加载这个类,然后触发其初始化过程,这包括执行静态代码块。 Class.forName() 方法的主要用途是在运行时动态地加载和使用类,而不需要在编译时就知道这个类的存在。由于它是在运行时动态加载类的,所以它总是会触发类的加载和初始化过程(如果类还没有被加载和初始化的话)。
总结来说,Object.class 不会运行静态代码块是因为Object类通常在你开始执行任何Java代码之前就已经被加载和初始化了,而Class.forName("ClassName") 会运行静态代码块是因为它用于在运行时动态地加载和初始化类。
5. 关于静态代码块初始化的几个关键点:
执行时机:静态代码块在类被加载到JVM时执行。这通常发生在类的第一次主动使用时,例如创建类的实例、访问类的静态字段或方法、反射调用类的方法等。 执行顺序:如果有多个静态代码块,它们将按照在源文件中出现的顺序执行。 与静态变量的关系:静态变量可以在静态代码块中初始化,也可以在声明时直接赋值。但是,如果静态变量在声明时直接赋值,那么该赋值操作会在静态代码块之前执行。 静态代码块与构造器:静态代码块在构造器之前执行。这是因为静态代码块属于类级别的初始化,而构造器属于对象级别的初始化。 线程安全:虽然静态代码块只执行一次,但在多线程环境下,JVM会确保静态代码块的线程安全性,即静态代码块会同步执行,不会被多个线程同时访问。但是,请注意,静态代码块中的代码本身并不是线程安全的,你仍然需要确保其中的代码在多线程环境下能够正确运行。
6. 类中的静态代码块被初始化后保存在JVM的哪里?
在JVM中,类中的静态代码块(static code block)在初始化后被执行,但它们本身并不直接“保存”在JVM的某个特定区域。静态代码块中的代码逻辑在类加载时被执行一次,并且其执行结果(如静态变量的赋值)会被存储在JVM的以下区域:
- 方法区(Method Area):
类加载后,静态变量(包括在静态代码块中初始化的变量)会被存储在方法区中的静态变量区域。静态变量属于类变量,不属于对象实例的一部分,只会在类加载时初始化一次。
- 堆内存(Heap Memory): 虽然静态代码块本身不直接存储在堆内存中,但是静态代码块中初始化的静态对象(如静态的数组、其他类的静态实例等)会存储在堆内存中。这些对象通过静态变量持有引用,静态变量则存储在方法区的静态变量区域中。
- 常量池(Constant Pool):
如果静态代码块中声明了static final常量,并且这些常量的值在编译时就能确定(即常量折叠),那么这些常量会被放入类的常量池中。常量池是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。 需要注意的是,静态代码块只是类加载过程中的一个逻辑单元,用于在类加载时执行一些初始化操作。这些操作的结果(如静态变量的值)会被存储在JVM的相应区域中,而静态代码块本身并不作为一个独立的实体被保存。当类被加载并初始化后,静态代码块中的代码逻辑就完成了它的使命,后续的对象创建和方法调用都不会再次执行这些代码。
7. java 类级别的初始化和对象级别的初始化的区别?
在Java中,类级别的初始化和对象级别的初始化是两个不同的概念,它们分别涉及类的静态部分和实例部分。以下是它们之间的主要区别:
类级别的初始化
执行时机:当类被JVM首次主动使用时,会触发类级别的初始化。这通常发生在创建类的第一个实例、访问类的静态字段或方法、或者通过反射调用类的方法等情况。
初始化内容:类级别的初始化包括执行静态代码块(static blocks)和初始化静态变量。静态代码块和静态变量的初始化顺序按照它们在源代码中出现的顺序进行。
执行次数:类级别的初始化只会在类首次被加载到JVM时执行一次,无论创建多少个类的实例,静态代码块都不会再次执行。
线程安全性:JVM会确保类级别的初始化是线程安全的,即静态代码块会同步执行,不会被多个线程同时访问。
对象级别的初始化
执行时机:当创建类的实例时,会触发对象级别的初始化。这通常通过调用类的构造函数来完成。
初始化内容:对象级别的初始化包括执行实例初始化块(instance blocks)和构造函数,以及初始化实例变量。实例初始化块和构造函数的执行顺序是:首先执行实例初始化块(按照它们在源代码中出现的顺序),然后执行构造函数。
执行次数:与类级别的初始化不同,对象级别的初始化会在每次创建类的实例时执行。
线程安全性:对象级别的初始化(即构造函数的执行)不是线程安全的。如果在多线程环境中创建类的实例,需要确保对共享资源的访问是同步的,以避免数据不一致或其他并发问题。