关于java类加载

文章详细阐述了JVM的内存架构,包括虚拟机栈、堆、方法区等,并介绍了类加载的生命周期,如加载、验证、准备、解析和初始化。重点讨论了`Class.forName`方法与`newInstance()`的区别,以及静态变量的加载顺序和类初始化的细节。同时,文章探讨了多线程环境下类初始化的同步机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 1 JVM整体架构
根据JVM规范,JVM内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

JAVA类的加载过程

在Run的时候,先将.java文件编译成.class文件。然后,在通过类加载器,将class文件加载到JVM中,然后在运行。输出结果。

类加载的生命周期包括:加载Loading,验证Verification, 准备Preparation,解析Resolution, 初始化Initialization,使用Using和卸载Unloading.

Java中用import导入类和用Class方法加载类有什么区别

import仅仅包含导入操作,并不包含将字节码文件加载进内存这一动作,将字节码文件加载进内存是后续的实例化操作完成的。例如通过import导入了一堆包和类,但是后续什么都没用(没用实例化),那么导入的东西是不会被加载进内存的。而且import是编译期的,如果你在后续代码中没有使用到你导入的内容,那么import语句甚至不会编译和执行。查看字节码文件可以看出,import的作用就是对你程序中要用到(实例)的东西进行署名(signature),当程序运行的时候好知道你实例化的对象的类的字节码文件去哪里找。

而Class.forName方法包含的动作是:根据给出的全类名(方法的参数)找到对应的字节码文件,并将字节码文件通过ClassLoader加载进内存中生成Class类对象(方法的返回值就是Class类对象)。

A a = (A)Class.forName("pacage.A").newInstance();这和 A a =new A();是一样的效果

Class类的常用方法

1、getName() 

一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

2、newInstance()

Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:
    x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。

3、getClassLoader() 

返回该类的类加载器。

4、getComponentType() 
    返回表示数组组件类型的 Class。

5、getSuperclass() 
    返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。

6、isArray() 
    判定此 Class 对象是否表示一个数组类。

加载

加载阶段是将class文件从磁盘或者jar等读到JVM内存中,并为其创建一个Class对象。任何一个类被使用时候系统都会为其创建一个Class对象的。

验证

验证是连接阶段的第一步,这一阶段的目的是 为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并却不会危害虚拟机自身的安全。

按照虚拟机规范,如果验证到输入的字节流不符合Class文件的存储格式,就抛出一个java.lang.VerifyError异常或其子类异常。

大致分成4个阶段的验证过程: 文件格式验证、 元数据验证、 字节码验证和 符号引用验证

准备阶段
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个时候 内 存分配的仅包括类变量(static变量),不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“ 通常情况下”是数据类型的零值( 随后在初始化阶段生成定义的初值)。 如果该变量被final修饰,将在编译时生成ConstantValue,这样在准备阶段将直接设置成该初值

解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

其实就是将堆内存空间里的静态变量符号修改为已经申请了空间的静态变量地址的过程

解析动作主要针对类/接口,字段,类方法,接口方法四类符号引用进行

可以确定如果成员变量没有new,是不会走的成员变量的类的初始化阶段。

初始化阶段

是类加载过程的最后一步,初始化阶段才真正开始执行类中定义的JAVA程序代码

准备阶段中,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的计划来赋值

或者说,初始化阶段是执行类构造器<clinit>()方法的过程

1.<clinit>()方法是由编译器自动收集类中的所有类变量的复制动作和静态语句块中的语句合并而成。编译器收集的顺序和语句在源文件中出现的顺序一致,静态语句块中只能访问到定义在它之前的变量,定义在它之后的变量,只能赋值,不能访问
2.<clinit>()方法与类的构造函数<init>()不同,不需要显式的调用父类构造器,虚拟机会保证父类的3.<clinit>()在子类的之前完成。因此,虚拟机执行的第一个<clinit>()方法肯定是java.lang.Object.
4.由于父类<clinit>()方法先执行,也就意味着父类中定义的静态语句要优先于子类的变量赋值操作。
5.<clinit>()方法并不是必须的,如果一个类没有静态语句块也没有对变量赋值操作,就不会生成
接口中不能使用静态语句块,但仍有变量初始化赋值的操作,因此也会生成<clinit>()方法,但与类不同的是,接口的<clinit>()方法不需要执行父接口的<clinit>()方法。只有当父几口中定义的变量被使用时,父接口才初始化,另外,接口的实现类在初始化时一样不会执行接口的<clinit>()方法。
6.虚拟机会保证一个类的<clinit>()方法在多线程环境中正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都会阻塞,直到该方法执行完,如果在一个类的<clinit>()方法中有耗时很长的操作,可能会造成多个进程阻塞,在实际应用中,这种阻塞往往很隐蔽。

思考:

1.Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段和静态变量!

2.Class.forName 方法是指加载阶段,还是加载+连接阶段?加载+连接阶段

3.new关键字和newInstance()方法的区别: 

1. newInstance: 弱类型。低效率。只能调用无参构造。  
2. new: 强类型。相对高效。能调用任何public构造。

4.class会被回收吗? 会!

5.<clinit>()方法和构造函数

问题:

假设子类A,父类B,A的成员变量C

import XXX.C;

class A extends B{

static long gStartTime = System.currentTimeMillis();

  C c=null;

public A(){

LogUtil.d("langxm","A classlocader "+ gStartTime);
LogUtil.d("langxm","B Constructor "+ System.currentTimeMillis());

public func(){

c = new C();

}

}

结果:

B classlocader

A classlocader

B Constructor

A Constructor s

A Constructor e

C classlocader

##从结果来看,如果先调用class.forName(), 那么因为gStartTime是静态变量,会在此函数中执行。会与B的Constructor 有时间差。

##C 出现在真正加载的地方,说明import和成员变量不赋值不会走class.forName()

##C的时间点,能说明成员变量是在A的函数调用前执行的吗?

##为什么A这个类第一次加载慢,第二次快,可能是内部的成员已经初始化class.forName过了

### Flink 大数据处理优化技巧与最佳实践 #### 调优原则与方法概述 对于Flink SQL作业中的大状态导致的反压问题,调优的核心在于减少状态大小以及提高状态访问效率。通过合理配置参数和调整逻辑设计可以有效缓解此类瓶颈[^1]。 #### 参数设置建议 针对不同版本下的具体特性差异,在实施任何性能改进措施前应当充分理解当前使用的Flink版本特点及其局限性;同时也要考虑特定应用场景的需求特征来定制化解决方案。这包括但不限于并行度设定、内存分配策略等方面的选择[^2]。 #### 数据流模式优化 采用广播变量机制可作为一种有效的手段用于降低主数据流转过程中所需维护的状态量级。当存在一对多关系的数据集间需频繁交互时,将较小规模的一方作为广播状态保存下来供另一方查询匹配使用不失为明智之举。此方式特别适用于维表Join操作中,其中一方变动相对较少但又必须保持最新记录的情况[^3]。 ```sql -- 创建临时视图以支持后续JOIN操作 CREATE TEMPORARY VIEW dim_table AS SELECT * FROM kafka_source; -- 定义Temporal Table Function以便获取指定时间点上的历史快照 CREATE FUNCTION hist_dim_table AS 'com.example.HistoricalDimTableFunction'; -- 执行带有时态条件约束的JOIN语句 SELECT o.order_id, d.product_name FROM orders o LEFT JOIN LATERAL TABLE(hist_dim_table(o.event_time)) AS d ON o.product_id = d.id; ``` 上述代码片段展示了如何利用Flink SQL实现基于时间戳的历史维度表连接功能,从而确保每次都能准确捕捉到事件发生瞬间对应的最恰当的产品名称信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值