JAVA基础面试题总结(十四)——JVM(下)

news2024/9/21 16:19:42

类文件结构详解

什么是字节码?

在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。

类文件结构包括哪些?

魔数(即标志是否为类文件)、Class文件版本号、常量池、访问标志、

当前类、父类、接口索引集合

字段表集合、方法表集合、属性表集合

在这里插入图片描述

类加载过程详解

类的生命周期

类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期可以简单概括为 7 个阶段::加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。

这 7 个阶段的顺序如下图所示:
在这里插入图片描述

类的加载过程

Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?

系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析

在这里插入图片描述

一、加载

主要完成下面 3 件事情:

  1. 通过全类名获取定义此类的二进制字节流。

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

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

加载这一步主要是通过我们后面要讲到的 类加载器 完成的。类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载由 双亲委派模型 决定(不过,我们也能打破由双亲委派模型)。

二、验证

验证是连接阶段的第一步,这一阶段的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全

三、准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配

对于该阶段有以下几点需要注意:

  1. 这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被 static 关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
  2. 从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。
  3. 这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了public static int value=111 ,那么 value 变量在准备阶段的初始值就是 0 而不是 111(初始化阶段才会赋值)。特殊情况:比如给 value 变量加上了 final 关键字public static final int value=111 ,那么准备阶段 value 的值就被赋值为 111。

四、解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。也就是得到类或者字段、方法在内存中的指针或者偏移量

在这里插入图片描述

五、初始化

初始化阶段是执行初始化方法 <clinit> ()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。

说明:<clinit> ()方法是编译之后自动生成的。

类卸载

卸载类即该类的 Class 对象被 GC。

卸载类需要满足 3 个要求:

  1. 该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
  2. 该类没有在其他任何地方被引用
  3. 该类的类加载器的实例已被 GC

类加载器详解

类加载器介绍

类加载器是一个负责加载类的对象。类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中,并在内存中生成一个代表该类的 Class 对象

ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

从上面的介绍可以看出:

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
  • 每个 Java 类都有一个引用指向加载它的 ClassLoader
  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。

类加载器加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

类加载器介绍

JVM 中内置了三个重要的 ClassLoader

  1. BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jarresources.jarcharsets.jar等 jar 包和类)以及被 -Xbootclasspath参数指定的路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
  3. AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

rt.jar:rt 代表“RunTime”,rt.jar是 Java 基础类库,包含 Java doc 里面看到的所有的类的类文件。也就是说,我们常用内置库 java.xxx.*都在里面,比如java.util.*java.io.*java.nio.*java.lang.*java.sql.*java.math.*

Java 9 引入了模块系统,并且略微更改了上述的类加载器。扩展类加载器被改名为平台类加载器(platform class loader)。Java SE 中除了少数几个关键模块,比如说 java.base 是由启动类加载器加载之外,其他的模块均由平台类加载器所加载。

自定义类加载器

我们前面也说说了,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader抽象类。

ClassLoader 类有两个关键的方法:

  • protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制 。name 为类的二进制名称,resolve 如果为 true,在加载时调用 resolveClass(Class<?> c) 方法解析该类。
  • protected Class findClass(String name):根据类的二进制名称来查找类,默认实现是空方法。

如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

双亲委派模型

双亲委派模型介绍

ClassLoader 类使用委托模型来搜索类和资源。每个 ClassLoader 实例都有一个相关的父类加载器。需要查找类或资源时,ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。

虚拟机中被称为 "bootstrap class loader"的内置类加载器本身没有父类加载器,但是可以作为 ClassLoader 实例的父类加载器。

从上面的介绍可以看出:

  • ClassLoader 类使用委托模型来搜索类和资源。
  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
  • ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。

在这里插入图片描述

双亲委派模型执行流程

1、使用loadClass进行类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。

2、类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,即调用父加载器 loadClass()方法来加载类。这样的话,所有的请求最终都会传送到顶层的启动类加载器 BootstrapClassLoader 中。

3、只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载,即调用自己的 findClass() 方法来加载类。

4、如果子类加载器也无法加载这个类,那么它会抛出一个 ClassNotFoundException 异常。

JVM 判定两个 Java 类是否相同的具体规则:JVM 不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相同。

双亲委派模型的好处

1、避免类的重复加载:每个类加载器都有自己的命名空间,相同的类文件即使被不同的类加载器加载,也会被视为两个不同的类。这样可以避免类的重复加载,提高了系统的资源利用率。

2、保护核心 API 的完整性:由于核心 API 是由启动类加载器加载的,其他类加载器无法修改它们。这样可以确保核心类库的完整性和安全性,防止恶意代码篡改核心 API。

3、安全隔离:每个类加载器都只能访问自己加载的类和依赖的类,无法访问其他类加载器加载的类。这种隔离性可以有效地保护不同组件或模块之间的互相干扰,增加系统的安全性和稳定性。

如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现两个不同的 Object 类。双亲委派模型可以保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而 ExtClassLoader 又会委托给 BootstrapClassLoaderBootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。

打破双亲委派模型的方法

1、因为我们自定义的加载器,需要继承 ClassLoader 。所以如果想打破双亲委派模型则需要重写 loadClass() 方法。

因为在双亲委派执行流程中,类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,即调用父加载器 loadClass()方法来加载类。当我们重写 loadClass()方法之后,我们就可以改变传统双亲委派模型的执行流程。例如,子类加载器可以在委派给父类加载器之前,先自己尝试加载这个类,或者在父类加载器返回之后,再尝试从其他地方加载这个类。具体的规则由我们自己实现,根据项目需求定制化。

如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,

例如:

我们比较熟悉的 Tomcat 服务器为了能够优先加载 Web 应用目录下的类,然后再加载其他目录下的类,就自定义了类加载器 WebAppClassLoader 来打破双亲委托机制。每个 Web 应用都会创建一个单独的 WebAppClassLoader,这也是 Tomcat 下 Web 应用之间的类实现隔离的具体原理。

2、通过线程上下文加载器ThreadContextClassLoader,可以解决高层的类加载器需要加载低层的加载器才能加载的类。破坏了 Java 的类加载委托机制,让应用逆向使用类加载器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2069113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第二十八节、场景互动的逻辑实现

一、实现接口 mono后面加上接口类&#xff0c;然后实现方法 onenable在场景或物体关闭再打开的激活状态使用 二、绑定按键 三、场景转换 卸载当前场景&#xff1b;加载另一个场景&#xff1b;提供玩家的所处位置 将玩家位置粘贴过来

【Hexo】使用cloudflare pages自动化部署hexo

本文首发于 ❄️慕雪的寒舍 本文将教您使用cloudflare pages来白嫖部署hexo博客。 1.注册cloudflare 这部分就省略了&#xff0c;用邮箱注册就可以了 cloudflare pages的免费版本功能如下&#xff1a; 并发构建数&#xff1a;1&#xff08;如果有多个pages&#xff0c;同一…

搭建Windows环境下的Redis服务与TinyRDM客户端

Redis是一个开源的高性能键值对数据库&#xff0c;以其内存中数据存储和快速的读写能力而广受开发者欢迎。在Windows环境下搭建Redis服务并使用TinyRDM客户端&#xff0c;可以为开发和日常使用提供极大的便利。 安装Redis服务 1. 下载Redis安装包 首先&#xff0c;下载Redis…

conda install 报错:LibMambaUnsatisfiableError

出现这个错误 LibMambaUnsatisfiableError 通常是因为 Conda 无法在当前配置的通道中找到满足所有依赖项的软件包。 运行下面两个命令解决&#xff1a; conda config --add channels conda-forge conda install -c stanfordnlp stanza1.4.0 有些包可能不在默认的 Conda 通道中…

ECharts tooltip默认html样式,保留样式只对数值格式化

之前遇到过需要对数据进行百分比展示&#xff0c;echarts提供的默认样式还是挺好的所以想保留样式&#xff0c;但是设置了formatter默认样式就没了&#xff0c;所以写了formatter的html字符串模拟还原了一下默认样式&#xff0c;在此记录和分享。 适用场景&#xff1a;对数据进…

MySQL中 EXPLAIN 的使用介绍

Day08-11. MySQL-索引-结构_哔哩哔哩_bilibili 使用 EXPLAIN 在查询语句前加上 EXPLAIN 关键字&#xff1a; EXPLAIN SELECT * FROM orders WHERE customer_id 123 AND order_date > 2023-01-01; 执行上述 EXPLAIN 语句&#xff0c;查看输出结果。MySQL 会返回一个包含查…

Linux 可视化管理工具:Webmin

&#x1f600;前言 在 Linux 系统的运维管理中&#xff0c;命令行界面&#xff08;CLI&#xff09;是主要的操作方式。然而&#xff0c;对于许多系统管理员或开发者来说&#xff0c;使用 CLI 进行管理和维护任务并不总是最直观或最方便的方式。为了简化操作并提高效率&#xff…

LLama3本地部署安装

这篇教程将指导你如何在本机上安装 LLama3 客户端和可视化对话界面&#xff0c;我们会分为三部分&#xff1a;安装 LLama3 客户端、部署大模型和设置模型文件路径。 一、LLama3客户端安装 去 Ollama 官网下载 Ollama 客户端 Download Ollama on macOS 选择合适的操作系统平台…

Unity 编辑器-监听创建控件,prefab创建或添加组件的自动处理⭐

拓展控件 需求解决方案方案 需求 比如我想在添加Text时&#xff0c;自动添加一个脚本&#xff0c;用于处理多语言。在添加图片时&#xff0c;自动去掉raycast的勾选以节约性能损耗 解决方案 方案 ObjectFactory.componentWasAdded 用于监听组件的添加事件 using TMPro; us…

《基于智能化填报的单病种质量监测系统》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Python在QtSide6(PyQt)上加载网页使用OpenCV进行图像处理

基本思路&#xff1a; 1.在Qt Designer中添加QWebEngineView&#xff0c;该组件可用于加载网页 2.在python中开启Timer事件&#xff0c;每10ms进行一次网页窗口截图&#xff08;QWidget.grab&#xff09; 3.将截图&#xff08;QPixmap)转换为cv.mat&#xff0c;进行图像处理…

关于thinkPHP3.2中的rewrite不严谨问题会导致网站被注入以及nginx配置中的if多条件判断问题-阿里云阻止指host访问

一、关于thinkPHP3.2中的rewrite不严谨问题会导致网站被注入 thinkPHP3.2的rewrite问题&#xff0c;之前也没在意过&#xff0c;但是今天浏览了一下服务器的一套CMS的相关东西&#xff0c;因为这套CMS是使用thinkPHP3.2搭建的&#xff0c;在浏览代码时发现其目录架构与目前的流…

内网横向移动常用方法

横向移动 #横向移动含义 横向移动是以已经被攻陷的系统为跳板&#xff0c;通过收集跳板机的信息&#xff08;文档&#xff0c;存储的凭证&#xff0c;ipc连接记录等等信息&#xff09;来访问其他域内主机。#常见横向手段 1&#xff0c;通过相同的用户名密码批量ipc连接其他域内…

【C++二分查找 】1477. 找两个和为目标值且不重叠的子数组

本文涉及的基础知识点 C二分查找 C算法&#xff1a;滑动窗口总结 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode1477. 找两个和为目标值且不重叠的子数组 给你一个整数数组 arr 和一个整数值 target 。 请你在 arr 中找 两个互…

C语言 | Leetcode C语言题解之第368题最大整除子集

题目&#xff1a; 题解&#xff1a; int cmp(int* a, int* b) {return *a - *b; }int* largestDivisibleSubset(int* nums, int numsSize, int* returnSize) {int len numsSize;qsort(nums, numsSize, sizeof(int), cmp);// 第 1 步&#xff1a;动态规划找出最大子集的个数、…

Java | Leetcode Java题解之第371题两整数之和

题目&#xff1a; 题解&#xff1a; class Solution {public int getSum(int a, int b) {while (b ! 0) {int carry (a & b) << 1;a a ^ b;b carry;}return a;} }

【Python】函数内的全局变量关键字gobal值,在函数外调用,有块“免死金牌”

在python世界里&#xff0c;全局变量global关键字&#xff0c;在函数间的跳转&#xff0c;就像是被赋予独立于体系的全局者&#xff0c;拥有一块“免死金牌”。 他拥有一种特权——它只是函数内部的对象&#xff0c;但是却能在函数外&#xff0c;亮出自己身份的全局变量&#…

浅拷贝和深拷贝(图文详解)

前端面试中&#xff0c;面试官经常会提到关于浅拷贝和深拷贝的问题。但是我总是理解于它的表面&#xff0c;面试中再深挖一点就会卡壳&#xff0c;我想把我的理解写下来&#xff0c;希望可以帮助到大家&#xff0c;如果有错误的地方希望大家可以指正&#xff0c;以免误导~ 看这…

mysql的安装与初始化

mysql mysql5.7.40下载链接 mysql安装文档 1. 编译安装过程 yum install -y cmake # 安装cmake tar xf mysql-boost-5.7.40.tar.gz cd /root/mysql-5.7.40 cmake -LH # 查看cmake的默认参数&#xff0c;需要进入mysql目录 yum install -y gcc-c.x86_64 yum install -y bis…

漏洞挖掘 | 记一次伪静态页面的SQL注入

前言 最近工作繁忙&#xff0c;许久没有挖洞&#xff0c;打开度娘&#xff0c;凡事随缘&#xff0c;偶米头发~~(⊙﹏⊙) 涉及技能点 SQL注入基础原理 盲注常用函数及思路 burpsuite基础知识 过程记录 1.发现 在翻阅一EDU站点时&#xff0c;发现路径中带有明显的数字参数 …