首页 > 其他分享 >jvm 类加载机制

jvm 类加载机制

时间:2023-08-28 21:36:47浏览次数:42  
标签:初始化 字节 验证 虚拟机 引用 jvm 机制 加载

类加载机制

类加载机制是指我们将类的字节码文件所包含的数据读入内存,同时我们会生成数据的访问入口的一种特殊机制。那么我们可以得知,类加载的最终产品是数据访问入口。

虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.lang.Class

加载流程
装载、链接(验证、准备、解析)、初始化、使用、卸载

装载

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  • 类加载器 可以实现通过类全名来获取此类的二进制字节流这个动作,并且将这个动作放到放到java虚拟机外部去实现,以便让应用程序决定如何获取所需要的类
  1. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  2. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

获取类的二进制字节流的阶段是我们JAVA程序员最关注的阶段,也是操控性最强的一个阶段。
因为这个阶段我 们可以对于我们的类加载器进行操作,比如我们想自定义类加载器进行操作用以完成加载,又或者我们想通过 JAVA Agent来完成我们的字节码增强操作。

在我们的装载阶段完成之后,这个时候在我们的内存当中,我们的运行时数据区的方法区以及堆就已经有数据了。

  • 方法区:类信息,静态变量,常量
  • 堆:代表被加载类的java.lang.Class对象

即时编译之后的热点代码并不在这个阶段进入方法区

链接

验证

验证只要是为了确保Class文件中的字节流包含的信息完全符合当前虚拟机的要求,并且还要求我们的信息不会危害虚拟机自身的安全,导致虚拟机的崩溃。

  1. 文件格式验证

验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。

这阶段的验证是基于二进制字节流进行的,只有经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面验证都是基于方法区的存储结构进行的。

  • 是否以16进制cafebaby开头
  • 版本号是否正确
  1. 元数据验证

对类的元数据信息进行语义校验(其实就是对Java语法校验),保证不存在不符合Java语法规范的元数据信息。

  • 是否有父类
  • 是否继承了final类
    因为我们的final类是不能被继承的,继承了就会出现问题。
  • 一个非抽象类是否实现了所有的抽象方法
    如果没有实现,那么这个类也是无效的。

总结:对类的元数据信息进行语义校验(其实就是对Java语法校验),保证不存在不符合Java语法规范的元数据信息。

  1. 字节码验证

进行数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

对类的方法体进行校验分析,保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。

字节码的验证会相对来说较为复杂 。

  • 运行检查
  • 栈数据类型和操作码操作参数吻合(比如栈空间只有4个字节,但是我们实际需要的远远大于4个字节,那么这个时候这个字节码就是有问题的)
  • 跳转指令指向合理的位置
  1. 符号引用验证

这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段),

可以看作是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。符号引用验证的目的是确保解析动作能正常执行。

  • 常量池中描述类是否存在
  • 访问的方法或者字段是否存在且具有足够的权限

验证可以取消

-Xverify:none 取消验证

准备

类变量

  1. static修饰: 为类的静态变量分配内存,并将其初始化为默认值

通常情况下,初始值为零值,假设public static int a=1;那么a在准备阶段过后的初始值为0,不为1,这时候只是开辟了内存空间,并没有运行java代码,a赋值为1的指令是程序被编译后,存放于类构造器()方法之中,所以a被赋值为1是在初始化阶段才会执行。

  1. final static修饰: 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;

只有同时被final 和static修饰的字段才有ConstantValue属性,且限于基本类型和String。
因为从常量池中只能引用到基本类型和String类型的字面量

实例变量

  1. 实例变量 没有final和static: 这里不会为实例变量(也就是没加static)分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

解析

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

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

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

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

初始化

初始化阶段是执行类构造器()方法的过程。

或者讲得通俗易懂些, 在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,比如赋值。

在Java中对类变量进行初始值设定有两种方式:

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

按照程序员的逻辑,你必须把静态变量定义在静态代码块的前面。因为两个的执行是会根据代码编写的顺序来决定的,顺序搞错了可能会影响你的业务代码。

JVM初始化步骤:

  • 假如这个类还没有被加载和连接,则程序先加载并链接该类
  • 假如该类的直接父类还没有被初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统依次执行这些初始化语句

使用

那么这个时候我们去思考一个问题,我们的初始化过程什么时候会被触发执行呢?或者换句话说类初始化时机是什么呢?

主动引用
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用有六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射(如 Class.forName(“com.carl.Test”) )
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(JvmCaseApplication ),直接使用 java.exe 命令来运行某个主类

被动引用

  • 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
  • 定义类数组,不会引起类的初始化。
  • 引用类的static final常量,不会引起类的初始化(如果只有static修饰,还是会引起该类初始化的)。

卸载

在类使用完之后,如果满足下面的情况,类就会被卸载:

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。

Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。但是一般情况下启动类加载器加载的类不会被卸载,而我们的其他两种基础类型的类加载器只有在极少数情况下才会被卸载。

end

标签:初始化,字节,验证,虚拟机,引用,jvm,机制,加载
From: https://www.cnblogs.com/iniwu/p/17663416.html

相关文章

  • 关于onlyoffice完成安装配置后,文档一直加载中的坑
     坑:根据官方文档按顺序安装完erlang,Rabbit,PostgreSQL运行环境后,安装完onlyoffice,配置完成后测试时文档一直提示加载中,无任何其他报错,也没有报错日志,一番排查后初步认定为无法链接到数据库。通过再次阅读官方文档,发现是创建数据库时账号密码和安装时输入不一致导致的。。。调......
  • 从原理聊 JVM(五):JVM 的编译过程和优化手段
    一、前端编译前端编译就是将Java源码文件编译成Class文件的过程,编译过程分为4步:1准备初始化插入式注解处理器(AnnotationProcessingTool)。2解析与填充符号表将源代码的字符流转变为标记(Token)集合,构造出抽象语法树(AST)。抽象语法树每个节点都代表着程序代码中的一个语法结......
  • 从原理聊JVM(五):JVM的编译过程和优化手段 | 京东云技术团队
    一、前端编译前端编译就是将Java源码文件编译成Class文件的过程,编译过程分为4步:1准备初始化插入式注解处理器(AnnotationProcessingTool)。2解析与填充符号表将源代码的字符流转变为标记(Token)集合,构造出抽象语法树(AST)。抽象语法树每个节点都代表着程序代码中的一个语法结构,包含包......
  • JVM调优实战及常量池详解
    阿里巴巴Arthas详解 Arthas 是 Alibaba 在2018年9月开源的 Java诊断工具。支持 JDK6+,采用命令行交互模式,可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细,详见:https://alibaba.github.io/arthas  Arthas使用场景得益于Arthas强大且丰富的功......
  • 在 PHP 中,原生并没有提供内置的定时器机制,定时触发的守护进程,其中一个常见的方式是使
    <?phpclassTimerDaemon{private$logfile;private$fp;private$triggerInterval;//触发间隔,以秒为单位private$lastTriggerTime;publicfunction__construct($logfile,$triggerInterval){$this->logfile=$logfile;......
  • JVM 内存大对象监控和优化实践
    作者:vivo互联网服务器团队-LiuZhen、YeWenhao服务器内存问题是影响应用程序性能和稳定性的重要因素之一,需要及时排查和优化。本文介绍了某核心服务内存问题排查与解决过程。首先在JVM与大对象优化上进行了有效的实践,其次在故障转移与大对象监控上提出了可靠的落地方案。最后,总......
  • node实现终端加载中
    简介在node开发脚手架之类的工具中,我们可能会用到进度条或者百分比数字进度展示,但是使用console的话会每次都单独占一行,所以想要看看别的框架是怎么实现的;这里是借鉴了ora这个第三方node包,然后把主要逻辑给提取出来(代码比较短)。oragithub地址上代码constfs=require("fs")......
  • JVM系列四:生产环境参数实例及分析【生产环境实例增加中】
    javaapplication项目(非web项目)改进前:-Xms128m-Xmx128m-XX:NewSize=64m-XX:PermSize=64m-XX:+UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction=78-XX:ThreadStackSize=128-Xloggc:logs/gc.log-Dsun.rmi.dgc.server.gcInterval=3600000-Dsun.rmi.dgc.client.gcInt......
  • JVM系列一:JVM内存组成及分配
    java内存组成介绍:堆(Heap)和非堆(Non-heap)内存      按照官方的说法:“Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heapmemory)”。可以看出JVM主要管理两种......
  • 深入理解操作系统中进程与线程的区别及切换机制(上)
    进程所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程!一个进程可以包含一个或者多个线程,但对于CPU来说他就是一个任务而已;在......