JVM——类加载和垃圾回收

news2024/11/25 20:27:49

目录

前言

JVM简介

JVM内存区域划分

JVM的类加载机制

1.加载

双亲委派模型

2.验证

验证选项

3.准备

4.解析

5.初始化

触发类加载

JVM的垃圾回收策略 GC

一:找     谁是垃圾 

1.引用计数

2.可达性分析  (这个方案是Java采取的方案)。

二:释放垃圾对象

三种典型的策略

JVM实现思路


前言

我们在学习JVM的时候,其实里面的内容是非常之多的,但是里面的大部分内容都是属于八股,想要彻底搞明白,就需要看大量的关于JVM的源代码,JVM的源代码是C++写的。想要深入研究的可以去看看《深入理解Java虚拟机》这本书。

这篇文章主要针对JVM中的常见的面试题来展开。

JVM简介

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。
虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。
常见的虚拟机:JVM、VMwave、Virtual Box。
JVM 和其他两个虚拟机的区别:

  1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;
  2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪。

JVM 是一台被定制过的现实当中不存在的计算机。

JVM内存区域划分

JVM其实就是一个Java进程,Java进程也就是JVM会从操作系统这里申请一大块内存空间,给Java代码来使用。

JVM从操作系统申请的这块内存空间中,进行进一步的划分,给出了每块划分后的空间的不同用途。

其中,最核心的就是栈、堆、元数据区(方法区)。

  • 虚拟机栈是给Java代码来使用的,主要存放一些局部变量,还有维护方法之间的调用关系。
  • 本地方法栈则是给JVM内部的本地方法来使用的。
  • 堆上存放的就是new出来的对象、成员变量。
  • 程序计数器中存放的就是一个内存地址,这个内存地址就是下一个要执行字节码所在的地址,作用就是记录当前程序执行到那个指令了。

需要注意的是,堆和元数据区,在一个JVM 中只存在一份,也就是多个线程共享堆区和元数据区。

栈(本地方法栈和虚拟机栈)和程序计数器则是存在多份的,也就是每个线程都会有一份。

JVM的线程操作和操作系统的线程操作是一对一的关系。也就是说每次在Java代码中创建的线程都会在操作系统中有一个线程与之对应。

这里的面试题主要就是判断某个变量或者对象在JVM的那个区域?

例如下面代码:

void func() {
    Test t1 = new Test();
}

上述代码在一个方法里面我们实例化了一个Test对象。

 func方法是在元数据区以一些二进制的指令来存储的。

我们可以看到t1变量是一个在方法里面定义的,所以他是一个局部变量,局部变量就存储在栈上。

而new Test(); 这个对象的本体则是在堆上的。

其实像这里的关于JVM区域的面试题,我们只需要知道JVM的每个区域都是存储什么东西的就好了。

  • 虚拟机栈是给Java代码来使用的,主要存放一些局部变量,还有维护方法之间的调用关系。
  • 本地方法栈则是给JVM内部的本地方法来使用的。
  • 堆上存放的就是new出来的对象、成员变量。
  • 程序计数器中存放的就是一个内存地址,这个内存地址就是下一个要执行字节码所在的地址,作用就是记录当前程序执行到那个指令了。

JVM的类加载机制

对与一个类来说,他的生命周期是这样的:

 前面的5步也是类加载的过程和固定的顺序。我们主要研究前面的5步。

类加载具体就是把一个.class文件,也就是类编译后的文件,加载到内存中,得到了类对象这样的过程就称之为类加载。

一个程序想要运行,就需要把指令和数据加载到内存中。类加载就是做的这个事情。

下面是类加载的5个步骤:

1.加载

这里的加载过程其实简单,就是找到.class文件,然后读取文件的内容。

但是在找.class文件的这个过程中,会有一个非常重要的机制:双亲委派模型

双亲委派模型

在JVM中,加载类需要用到一组特殊的模块:类加载器。

在JVM中,内置了三个类加载器。

  • BootStrap ClassLoader    负责加载Java标准库中的类
  • Extension ClassLoader     负责加载一些非标准的但是是Sun/Oracle扩展库的类
  • Application ClassLoader    负责加载项目中自己写的类、以及第三方库中的类

当具体加载一个类的时候,他的过程是这样的:

需要先给定一个类的全限定类名,"java.lang.String"  这个类名是一个字符串的形式。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层BootStrap ClassLoader类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

具体可以参考下图:

2.验证

由于.class文件有着明确的数据格式(二进制的),这一阶段的主要目的就是确保Class文件中的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求。

验证选项

文件格式验证

字节码验证

符号引用验证……

3.准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

比如下面这样的代码:

public static int value = 123;

此时在准备阶段value的值并不是123,而是0。
 

4.解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

  • 符号引用:就是字符串常量在.class文件已经存在,但是他们只知道彼此之间的相对位置,并不知道自己在内存中的具体位置。
  • 直接引用:真正的加载到内存中,就会把字符串常量填充到内存中的特定地址上去。此时字符串引用的就是直接引用,(也就是Java中普通的引用)。

5.初始化

在初始化阶段,JVM才真正的执行类中编写的Java代码,将主导权交给应用程序,初始化阶段就是执行类的构造方法的过程。(类要是有父类,就需要先初始化父类,在初始化子类)。

触发类加载

注意:类加载这个动作不是说JVM一启动就会进行加载,因为JVM整体是一个懒加载的策略,也就是非必要,不加载。

以下三种请况就会加载:

  1. 创建了这个类的实例
  2. 使用了这个类的静态方法/静态属性
  3. 使用子类,会触发父类的加载

JVM的垃圾回收策略 GC

Java中的垃圾回收是为了帮助我们自动释放内存的一种机制。

面试题:为什么需要垃圾回收机制

因为在程序运行过程中,会向操作系统申请大量的内存空间,但是这些空间也有可能会消耗尽,因为不断地分配内存空间而不进行回收,就好像不停地生产生活垃圾而从来不打扫一样。

上面我们谈到了关于JVM的几个区域,那么垃圾回收释放的是那个区域的空间呢?

需要注意的是,栈和程序计数器是每个线程都会有一份的。他们会随着线程的销毁而一起销毁的。

而元数据区里面的存储的类对象,很少会进行销毁。

所以我们释放的就是堆中的空间。上面我们谈到堆中主要就是存放new 出来的对象的。

GC也就是以对象为单位进行释放的。(释放对象)

GC中主要分为两个阶段:

一:找     谁是垃圾 

Java通过引用来判断是否是垃圾对象,如果没有引用指向,就判定这个对象是垃圾。

1.引用计数

给对象安排一个额外的空间,保存了一个整数,表示该对象有几个引用指向它。Java实际上并没有采取这样的方案,(Python、PHP采用了这个方案)。

Test t1 = new Test();

 此时是有一个引用指向的,所以引用计数器为1。

如果代码变成这样:

Test t1 = new Test();
Test t2 = t1;

 也就是说随着引用的增加,计数器就会增加,引用的销毁,计数器就会减少。

当计数器为0时,就会认为该对象没有引用指向了,就是垃圾了。

但是缺点也是很明显:

  1. 浪费内存空间
  2. 存在循坏引用的情况

2.可达性分析  (这个方案是Java采取的方案)。

把对象之间的引用关系理解成为了一个树形结构,从一些特殊的起点出发,进行遍历,只要能访问到,是可达的,不是垃圾,再把不可达的当做垃圾即可。

 此时通过root这个引用是可以访问到整个树的任意节点的。

可达性分析的关键要点在于要进行上述的遍历,需要有起点的。

起点可以是:

  1. 栈上的局部变量(每个栈的每个局部变量都是起点)
  2. 常量池中引用的对象
  3. 方法区中静态成员引用的对象

可达性分析,总体就是从所有的起点出发,看看该对象里面又通过哪些引用能访问到那些对象,顺藤摸瓜的把所有可以访问的对象都访问一遍,遍历的同时把对象标记为“可达”。

可达性分析,克服了引用计数的两个缺点

但是也是有自己的问题:

  • 消耗更多的时间 因此即使某个对象成了垃圾,也不能第一时间发现,因为在扫描的过程中,也是需要时间的。
  • 在进行可达性分析的时候,要顺藤摸瓜,一旦这个过程中,当前代码中的对象的引用关系发生了变化,就可以出现bug。

因此为了更好的完成这个顺藤摸瓜的过程,就需要让其他的业务线程都暂停工作!!!(STW)

(STW)   stop the world !

但是Java毕竟发展了这么多年,拉进回收这里也是在不断的进行优化,STW这个问题也可以比较好的对付了。

二:释放垃圾对象

三种典型的策略

1:标记清除

 如果现在向内存申请了一块下面这样的空间,然后我标出来的就是垃圾对象,需要清除的。

 这种策略就是直接把垃圾对象的内存就释放了。

但是这种简单粗暴的方式会产生内存碎片。

内存碎片:申请空间都是连续的整块空间,现在上述图中的空闲空间都是散落在独立的空间里面的。现在空闲总空间可能超过1G,但是我想申请500M,却是申请不了。

2:复制算法

这种方法是把空间分为两部分。一次只使用一半。

复制算法就是把不是垃圾的对象拷贝到一边去,然后在统一释放整个区域。

 此时我要释放的是2和4,我就需要把剩下1和3复制到另一边去。然后再把这边全部释放。

 复制算法解决了内存碎片的问题,但是也有缺点:

  • 内存利用率比较低
  • 如果大部分对象都是保留的,垃圾很少,此时的复制成本就比较高

3:标记整理

类似于顺序表删除中间元素,有一个搬运的过程

 解决了内存碎片问题但是搬运的整体开销也是比较大的。

JVM实现思路

实际上,JVM的实现方式是结合了上述几种思想之后的方法。

分代回收思想

具体细节:

  • 给对象设置年龄这样的概念,用来描述这个对象存在多久了。如果一个对象刚诞生,那么就是0岁。
  • 每次进过一次扫描(可达性分析)如果没有被标记为垃圾对象,这是对象年龄就增加一岁。
  • 通过年龄来区分这个对象的活动时间。

经验规律:年龄越大的对象,也将会持续存在更长的时间。

针对不同的年龄来采取不同的回收策略

JVM针对这几个区域来执行不同的策略。

1:新创建的对象,放在伊甸区

垃圾回收扫描到伊甸区之后,大多数的对象将会在第一轮扫描下被GC给淘汰掉。

2:如果伊甸区的对象,熬过第一轮GC,就会通过复制算法,拷贝到生存区。

生存区分为两半(大小相等),一次只使用其中的一半。

如果GC在扫描生存区的时候,发现垃圾对象也就淘汰,不是垃圾的,就通过复制算法拷贝到生存区的另一边。

3:当对象在生存区熬过了若干次GC的时候,年龄也变大了。此时就会通过复制算法拷贝到老年代。

4:进入老年代之后,由于年龄都比较大了,被标记为垃圾对象的概念也很小,所以针对老年代的GC扫描也会降低频率。

特殊情况:如果对象非常大,直接进入老年代(大对象进行复制算法,成本非常高,而且大对象也不会很多)。

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

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

相关文章

k210学习篇(一)环境搭建

一、为什么选择Canmv开发板? 便宜!便宜!便宜!淘宝200即可买到一个能带摄像头和LCD屏等等的开发板 二、利用Maix Hub在线训练 Maix Hub官网https://maixhub.com/home Maix Hub使用教程:K210学习笔记——MaixHub在线训练模型(新版) 注意: 三、配置开发环境 1.MaixPy IDE…

某网站提交登陆信息加密JS逆向实战分析

1. 写在前面 对于爬虫开发者来说,职业生涯中可能或多或少会遇到各种各样的网站,其中有些必要要求登陆才能浏览。那么模拟登陆的时候发现提交的登陆信息(用户名、密码)都是经过加密后的,如何处理?这里找到了…

我只改五行代码,接口性能提升了 10 倍!

背景 某公司的一个 ToB 系统,因为客户使用的也不多,没啥并发要求,就一直没有经过压测。这两天来了一个“大客户”,对并发量提出了要求:核心接口与几个重点使用场景单节点吞吐量要满足最低500/s的要求。 当时一想&am…

C++ IO流

文章目录 C语言的输入与输出流是什么?CIO流C标准IO流C文件流 stringstream的简单介绍 C语言的输入与输出 在C语言中,我们使用最频繁的输入输出方式为: scanf 和 printf. scanf : 从输入设备(键盘)读取数据,并将值存放在变量中.printf: 将指定的文字/字符串输出到标准输出设备…

数据库约束与表的关系(数据库系列4)

目录 前言: 1.数据库的约束 1.1约束类型 1.1.1 not null 1.1.2 unique 唯一约束 1.1.3 default 默认值约束 1.1.4 primary key 主键约束 1.1.5 foreign key 外键约束 2.表的关系 2.1 一对一 2.2 一对多 2.3 多对一 3.新增 4.聚合查询 4.1聚合函数 4.…

Pinecone - 向量数据库

文章目录 关于 PineconeRoadMapSemantic SearchChatbots购买查看 API Key创建索引代码调用安装库 pinecone-client查看已经创建的索引创建索引插入数据获取索引统计分析信息查询索引,获取相似向量删除索引关于 Pinecone 官网 : https://www.pinecone.i

利用layui构建OA系统基本操作

一.编写方法(增加,删除,修改,查询) 通过继承BaseDao来实现通用,从而减少代码量,提高小路 1.增加 public int add(User user) throws Exception {String sql "insert into t_oa_user(name…

Windows7中使用SRS集成音视频一对一通话

SRS早就具备了SFU的能力,比如一对一通话、多人通话、直播连麦等等。在沟通中,一对一是常用而且典型的场景, 让我们一起来看看如何用SRS做直播和RTC一体化的一对一通话。 一、启动windows7-docker 二、拉取SRS镜像 执行命令:docker pull oss…

修改jar包中的文件内容

文章目录 导引查找是否存在需要修改的文件vim命令修改配置文件jar命令替换jar包中的文件(也可新增)解压jar包,修改后重新打包jar修改clas文件jar命令参数 导引 首先问问为什么要直接修改jar包中的文件,而不是重新打包,在非必要的情况下&…

【动手学深度学习】pytorch-参数管理

pytorch-参数管理 概述 我们的目标是找到使损失函数最小化的模型参数值。 经过训练后,我们将需要使用这些参数来做出未来的预测。 此外,有时我们希望提取参数,以便在其他环境中复用它们, 将模型保存下来,以便它可以在…

python数据挖掘基础环境安装和使用

文章目录 一.安装python环境二、库的安装2.1 使用pip命令安装virtualenvv扩展:cmd无法使用pip,报错:Fatal error in launcher: Unable to create process using ... 2.2 安装virtualenvwrapper-win2.3 新建一个用于人工智能环境的…

腾讯云 API 3.0(V3版签名) 通用接口 Delphi 版

目录 一、腾讯云API 3.0 简介: 二、Delphi 接口函数说明: 1. Delphi 接口包含的单元: 2. 同步调用和异步调用的区别: 3. 程序调用示例: 三、Delphi 版腾讯云API 3.0 版接口函数下载 四、演示程序录播 下载源程序…

DB2数据库SQL将不同行做合计

DB2数据库SQL将不同行做合计 案例: 将’GL’和’RZ’做合计,其他的不动。 SELECT SALE_TYPE,ROUND(CAST(SUM(aatp_weight) AS DOUBLE),2) AS aatp_weight FROM( SELECT CASE WHEN SALE_TYPE GL THEN RZ ELSE SALE_TYPE END AS SALE_TYPE, DEMAND_NUM / …

基于NXP iMX8MP处理器M7核心LVGL移植

By Toradex胡珊逢 LVGL (Light and Versatile Graphics Library)是一个轻量级的开源图形库,采用 C 或者 MicroPython 语言开发。可以在资源有限的 MCU 上轻松地绘制图形界面。Verdin iMX8M Plus 模块的处理器除了 Cortex-A53 核心外,还具有一个 Cortex-M…

如何运营校园外卖跑腿小程序

运营校园外卖跑腿小程序需要考虑多个方面,包括市场调研、合作伙伴选择、用户获取与留存、服务管理和推广等。下面是一些关键步骤和策略: 市场调研: 在开始运营之前,进行市场调研是非常重要的。了解目标用户的需求和习惯&#xf…

HarmonyOS学习路之方舟开发框架—方舟开发框架(ArkUI)概述

方舟开发框架(简称ArkUI)为HarmonyOS应用的UI开发提供了完整的基础设施,包括简洁的UI语法、丰富的UI功能(组件、布局、动画以及交互事件),以及实时界面预览工具等,可以支持开发者进行可视化界面…

JVM系列(6)——类加载器详解双亲委派

一、类加载器 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。 主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。 加载过程可以看 JVM系列&a…

人工智能-反向传播

前面阐述过,在设计好一个神经网络后,参数的数量可能会达到百万级别,利用梯度下降去更新参数计算复杂,算力不足,因此需要一种有效计算梯度的方法,这种方法就是辛顿提出的反向传播(简称BP&#xf…

【算法基础】搜索与图论

DFS 全排列问题 842. 排列数字 - AcWing题库 #include<bits/stdc.h> using namespace std; const int N10; int n; int path[N]; bool st[N]; void dfs(int x) {if(x>n){for(int i1;i<n;i) cout<<path[i]<<" ";cout<<endl;return ;…

高级测试工程师求职之路:从笔试到面试,我经历了什么?

最近行业里有个苦涩的笑话&#xff1a;公司扛过了之前的三年&#xff0c;没扛过摘下最近的一年&#xff0c;真是让人想笑又笑不出来。年前听说政策的变化&#xff0c;大家都满怀希望觉得年后行情一片大好&#xff0c;工作岗位激增&#xff0c;至少能有更多的机会拥抱未来。然而…