Jvm 类加载机制解析,一起来了解神秘的类加载机制吧

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过 加载,连接,初始化,这三个步骤对类进行初始化,如果没有意外,JVM 将会连续完成这三个步骤,所以有时也称为类初始化。

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过 加载,连接,初始化,这三个步骤对类进行初始化,如果没有意外,JVM 将会连续完成这三个步骤,所以有时也称为类初始化。

虽然我们并无序过分关心类加载机制,但是 基本 工作机制我们还是要知道的,这样对我们来说也能更好的去理解。先用一张思维导图来概括类加载机制。不过需要注意的是,jdk1.9对类加载器进行了改变,并废除了一些方法,本篇并没有对 jdk1.9进行概述。

类加载 (划重点)

类加载指的是将类的 class 文件读入内存,并为之创建一个 Java.lang.class 对象,也就是说,当程序中使用任何类时,系统都会为之创建 一个 java.lang.Class 对象。

类的加载由加载器完成,类加载器通常由 jvm 提供,这些类加载器也是前面所有程序运行的基础,JVM 提供的这些类加载器通常被称为系统类加载器。除此之外,还可以通过继承 ClassLoader 基类来创建自己的类加载器。一般有如下几种类加载来源:

  • 从本地文件系统加载class 文件
  • 从jar包记载 class 文件
  • 通过网络加载class文件
  • 把一个java源文件动态编译,并执行加载。

类加载器通常无序等到 首次使用该类时才加载,Java 虚拟机允许系统预加载某些类。

当 JVM 启动时(也就是你点击运行程序之后),会形成有三个类 加载器组成的初始类加载器层次结构:

  1. Bootstrop ClassLoader ,被称为引导(也成为原始或根) 类加载器。他负责加载 Java的核心类。 我们一般使用的 Math,String,System类就是由此加载。
  2. Extension Classloader ,被称为扩展类加载器,他负责加载 jre 的扩展目录中 jar 包的类。通过这种方式,我们就可以为 java 扩展核心类 以外的新功能。
  3. System Classloader,系统(也称应用) 类加载器,该加载器继承自扩展类加载器,主要功能是加载我们在日常编译器(idea,eclipse等)里面编写的类,这些类的加载,都是由该类加载器完成的。程序可以通过 ClassLoader 的静态方法 getSysemClassLoader() 获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以类加载器作为父加载器。

类加载机制主要有以下三种

  • 全盘负责:当一个类加载器加载某个 Clas时,该Class 所依赖和引用的其他 Class 也将于该类加载器负责载入,除非显示的使用另一个类加载器来载入。
  • 双亲委派: 当一个类加载器收到了类加载请求,它会先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径加载该类,如果直至引用程序类加载器还没有发现该类同名的类,则会抛出异常。举个简单例子,你可以自定义一个 java.lang.Math类,但你永远永不了该类,因为该类存于 java 核心类,会由引导加载器去加载,所以不论你怎么调用,它加载的永远是 java 核心类库的 Math类。
  • 缓存机制: 缓存机制会保证所有加载过的 Class 都会被缓存,当程序中使用某个Class时,类加载器首先从缓存区寻找 该 Class,只有当缓存区中不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转换成 Class对象。这也就是为什么修改了 Class之后,必须重新运行程序才会生效的原因。

但需要注意,类加载器之间并不是继承上的父子关系,而是类加载器实例之间的关系。

连接

当类被加载之后,系统为之生成一个对应的 Class 文件,接着将会进入连续阶段,链接阶段负责把 类的二进制数据合并到 jre中,类链接又分为如下3个阶段。

  1. 验证: 验证阶段同于检验被加载的类是否有正确的内部结构,就是检查符不符合Java语言规范,并和其他类协调一致。
  2. 准备:类准备阶段则负责为类的类变量分配内存,并设置默认初始值。
  • 八种基本类型默认的初始值为0
  • 引用类型默认的初始值是 null
  • 有 static final 修饰的会直接赋值,例如 static final a=10,则默认就是10
  1. 解析:将类的二进制数据中的符号引用替换成直接引用。就是jvm 会把所有的类或接口名,字段名,方法名转换为具体的内存地址。

初始化

虚拟机负责对类进行初始化,主要就是对类 变量 进行初始化,有如下两种初始化方式:

  • 声明类变量时初始化
  • 使用静态初始化为类变量指定初始值
  • 一般初始化一个类包含如下几个步骤:
  1. 假如这个类没有被加载和连接,则程序先加载并连接该类。
  2. 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
  3. 假如该类中有初始化语句,则系统一次执行这些初始化语句。
  4. 一个类什么时候会被初始化呢?
  • 创建类的实例。为某个类创建实例的方法会包括:使用new 操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
  • 调用某个类的类方法(静态方法)。
  • 访问某个类或接口的类变量,或为该类变量赋值。
  • 使用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象。
Class.forName("Person") 
//如果系统还未初始化 一个 类,则这行代码将会导致该类被初始化,并返回该 类对应的 java.lang.Class对象。
  • 初始化某个类的子类。当初始化某个类的子类时,盖子类的所有父类就会被初始化。
  • 直接使用 java.exe 命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。
  • 不过也需要注意以下几种情形:
  • 对于一个 final 型的变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于 “宏变量” 。java 编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态类变量,也不会导致该类的初始化。
public class MyClass {
    //使用静态初始化块打印的语句
    static {
        System.out.println("123");
    }
    //声明变量a时指定初始值
    static final int a = 5;
}
 class Demo{
     public static void main(String[] args) {
         //并不会初始化Myclass
         System.out.println(MyClass.a);
     }    
}

反之,如果final修饰的类变量的值不能在编译时确定下来,则必须等到运行时才可以确定该类变量的值,通过该类来访问它的类变量,则会导致该类被初始化。

public class MyClass {
    static {
        System.out.println("静态方法块");
    }
    //运行时才可以确定的变量,就算加了final也不行
    static  final String com=System.currentTimeMillis()+"";
}
class Demo{
    public static void main(String[] args) {
        System.out.println(  MyClass.com);
    }
}

当某个变量使用了 final 修饰,而且他的值可以在编译时就确定下来,那么程序其他地方使用该变量时,实际上并没有使用该变量的值,而是相当于使用常量。

目录
相关文章
|
4天前
|
存储 缓存 监控
深入JVM:解析OOM的三大场景,原因及实战解决方案
深入JVM:解析OOM的三大场景,原因及实战解决方案
|
4天前
|
存储 监控 Java
深入理解Java虚拟机-类加载连接和初始化解析
深入理解Java虚拟机-类加载连接和初始化解析
|
2天前
|
存储 缓存 监控
JVM中G1垃圾收集器:原理、过程和参数配置深入解析
JVM中G1垃圾收集器:原理、过程和参数配置深入解析
|
3天前
|
Java UED 开发者
JVM逃逸分析原理解析:优化Java程序性能和内存利用效率
JVM逃逸分析原理解析:优化Java程序性能和内存利用效率
|
3天前
|
存储 缓存 监控
深入解析JVM内存分配优化技术:TLAB
深入解析JVM内存分配优化技术:TLAB
|
10天前
|
Java 编译器
全面解析JVM加载中初始化的时机
全面解析JVM加载中初始化的时机
|
6天前
|
存储 Java C++
Java虚拟机(JVM)在执行Java程序时,会将其管理的内存划分为几个不同的区域
【6月更文挑战第24天】Java JVM管理内存分7区:程序计数器记录线程执行位置;虚拟机栈处理方法调用,每个线程有独立栈;本地方法栈服务native方法;Java堆存储所有对象实例,垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息;运行时常量池存储常量;直接内存不属于JVM规范,通过`java.nio`手动管理,不受GC直接影响。
16 5
|
5天前
|
存储 Java 对象存储
jvm内存模型剖析
当线程cpu时间片执行完后,线程进入休眠状态,当再次唤醒时,通过程序计数器确定指令执行到哪一行,然后继续往下执行。
17 1
|
6天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
18 2
|
10天前
|
监控 算法 Java
Java虚拟机(JVM)使用多种垃圾回收算法来管理内存,以确保程序运行时不会因为内存不足而崩溃。
【6月更文挑战第20天】Java JVM运用多种GC算法,如标记-清除、复制、标记-压缩、分代收集、增量收集、并行收集和并发标记,以自动化内存管理,防止因内存耗尽导致的程序崩溃。这些算法各有优劣,适应不同的性能和资源需求。垃圾回收旨在避免手动内存管理,简化编程。当遇到内存泄漏,可以借助VisualVM、JConsole或MAT等工具监测内存、生成堆转储,分析引用链并定位泄漏源,从而解决问题。
24 4

推荐镜像

更多