【JVM】浅看JVM的运行流程和垃圾回收

news2024/12/30 1:51:50

1.JVM是什么

JVM( Java Virtual Machine)就是Java虚拟机。

Java的程序都运行在JVM中。

2.JVM的运行流程

JVM的执行流程:

在这里插入图片描述
程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。

2.1类加载子系统

对于一个类来说,它的生命周期是这样的:
在这里插入图片描述所以对于类加载来说,总共分为以下几个步骤:

  1. 加载
  2. 连接
    ①验证
    ②准备
    ③解析
  3. 初始化

1.连接

在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

总结:读取.class文件

2.连接

①验证

验证.class是否符合JVM的规范。

验证选项:

  • 文件格式验证
  • 字节码验证
  • 符号引用验证…

②准备

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

比如此时有这样一行代码:

public static int value = 123;

它是初始化 value 的 int 值为 0,而非 123。

③解析

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

3.初始化

初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。

4.双亲委派模型

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

在这里插入图片描述

如上图所示:

  • BootStrap :启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。
  • ExtClassLoader:扩展类加载器。加载 lib/ext 目录下的类。
  • AppClassLoader:应用程序类加载器:加载我们写的应用程序。
  • 自定义类加载器:根据自己的需求定制类加载器。

在这里插入图片描述

new一个我们自己创建的类时,先向上加载,到扩展类,如果没有找到这个类,再向上加载,询问启动类是否有,如果没有,再向下加载,一直到我们写的应用程序。

避免了恶意代码去修改JDK的风险。

2.2运行时数据区

JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型((Java Memory Model,简称JMM)完全不同,属于完全不同的两个概念)

1.方法区

存放的是类对象,可以理解为对象的模板。(存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据)

在《Java虚拟机规范中》把此区域称之为“方法区”,而在 HotSpot 虚拟机的实现中,在 JDK 7时此区域叫做永久代(PermGen),JDK 8 中叫做元空间(Metaspace)。

2.堆

存放的是new出来的对象。真正的对象的地址。

3.JVM虚拟机栈

每一个线程都有对应一个Java虚拟机栈,每调用一个方法都会以栈帧的形式加入到线程的栈中,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,方法执行完成之后栈帧就会被调出栈。栈主要记录的是方法的调用关系,还有可能会出现的栈溢出的错误。

在这里插入图片描述

4.本地方法栈

本地方法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使用的,而本地方法栈是给本地方法使用的。

5.程序计数器

记录当前线程的方法执行到哪一行。

3.垃圾回收

当方法结束或者线程结束时,内存就会跟着线程被回收。这种就称之为垃圾回收。
java堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经"死去"。判断对象是否已"死"有如下几种算法。

3.1垃圾回收的过程

在这里插入图片描述

  • 新生代:一般创建的对象都会进入新生代的Eden中;
  • 老年代:大对象和经历了 N 次(一般情况默认是 15 次)垃圾回收依然存活下来的对象会从新生代 移动到老年代。
  • 老年代的大小时新生代的两倍。

垃圾回收的过程如下:

首先,创建对象进入Eden中:
在这里插入图片描述当Eden满了之后,(下图)将还活着的对象移入S0中,剩余的都是“死去“的对象(打红叉的对象为已死的对象),清空所有死去对象(垃圾回收)。再次创建新的对象。
在这里插入图片描述
当Eden又满了之后,(下图)将还活着的对象移入S1中,清空所有”死去的对象“。再次创建新的对象。
在这里插入图片描述交换S1和S0.
在这里插入图片描述当Eden又满了之后,(下图)将还活着的对象移入S0中,清空所有”死去的对象“。交换S1和S0.
在这里插入图片描述重复上述过程。存活了15轮的对象会被放入老年区,当老年区满了之后会进行一次老年区的垃圾回收。

3.2死亡对象的判断算法(JVM采用)

1.引用计数算法

给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任
何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。

但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的
循环引用问题。

示例:

public class Test {
 	public Object instance = null;
 	private static int _1MB = 1024 * 1024;
 	private byte[] bigSize = new byte[2 * _1MB];
 	public static void testGC() {
 		Test test1 = new Test();  //第1行
 		Test test2 = new Test();  //第2行
 		test1.instance = test2;  //第3行
 		test2.instance = test1;  //第4行
 		test1 = null;  //第5行
 		test2 = null;  //第6行
 		// 强制jvm进行垃圾回收
 		System.gc();
	 }
 	public static void main(String[] args) {
 		testGC();
 	}
}

在这个代码中,编号按照代码中注释给出,第1,2行分别调用了GCDemo01一次,那么在堆上它们的计数器分别+1。第3,4行又分别再次调用了GCDemo01一次,那么在堆上它们的计数器都变为2.。但在第5,6行中,计算器虽然分别都减一,但test1和test2的instance再也无法访问到,所以堆中的引用计数器无法归0,导致垃圾无法被回收。
在这里插入图片描述

2.可达性分析算法

通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。以下图为例:

有引用关系的就会被标记为灰色。
在这里插入图片描述
对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象。

在Java语言中,可作为GC Roots的对象包含下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中 JNI(Native方法)引用的对象

3.3垃圾回收算法

通过上面的算法我们可以将死亡对象标记出来了,标记出来之后我们就可以进行垃圾回收操作了,在正式学习垃圾收集器之前,我们先看下垃圾回收机器使用的几种算法(这些算法是垃圾收集器的指导思想)。

1.标记-清除算法

"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不足加以改进而已。

"标记-清除"算法的不足主要有两个 :

  1. 效率问题 : 标记和清除这两个过程的效率都不高
  2. 空间问题 : 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

在这里插入图片描述

2.复制算法(新生代使用)

"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。算法的执行流程如下图 :
在这里插入图片描述

3.标记-整理算法(老年代使用)

复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。

针对老年代的特点,提出了一种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。流程图如下:

优点:在回收过后多了一步整理内存的工作
缺点:可以有大量连续的内存空间

在这里插入图片描述

3.4垃圾收集器(7种)

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

垃圾收集器的作用:垃圾收集器是为了保证程序能够正常、持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间。

垃圾收集器不断更新的目的减少STW的时间(Stop The World)(STW:每次进行垃圾回收的时候,程序会进入暂停状态)

以下这些收集器是 HotSpot 虚拟机随着不同版本推出的重要的垃圾收集器:
在这里插入图片描述

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们之间可以搭配使用。

  • Serial收集器:Serial 收集器是最基本、发展历史最悠久的收集器,这个收集器是一个单线程的收集器,是虚拟机运行在Client模式下的默认新生代收集器,与其他收集器的单线程比简单而高效。与Serial Old配对使用。
  • ParNew收集器:其实就是Serial收集器的多线程版本。对于Serial的优化,从串行变成并行,用多线程的方式扫描内存,提高垃圾回收的效率,减少STW的时间。
  • Parallel Scavenge收集器(新生代收集器,并行GC): Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。与Parallel Old配对使用。
  • CMS收集器(老年代收集器,并发GC):使用的是三色标记算法。
  • G1收集器(唯一一款全区域的垃圾回收器):G1收集器不分老年代和新生代。

补充

1.Minor GC和Full GC的区别

面试题 : 请问了解Minor GC和Full GC么,这两种GC有什么不一样吗?

  1. Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
  2. Full GC 又称为老年代GC或者Major GC : 指发生在老年代的垃圾收集。出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

2.4个引用

在这里插入图片描述

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

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

相关文章

Visio/PPT/Matlab输出300dpi以上图片【满足标准投稿要求】

1. visio 遵照如下输出选项,另存为tif格式文件时,选择正确输出便是300dpi以上 2. matlab 文件选项选中导出设置,在渲染中选择dpi为600,导出图片即可,科研建议选择tif格式文件 3.ppt 打开注册表,winr键…

【报错】在python3.9环境下安装sqlmap报错

问题描述 报错内容: missing one or more core extensions (‘ssl’, ‘sqlite3’) most likely because current version of Python has been built without appropriate dev packages 原因分析: 缺少一个或多个核心扩展(‘ssl’、‘sqlit…

常见的栈溢出StackOverFlow 与 内存溢出OutOfMemory的区别

0、前言:内存模型 对于多线程运行情况下的jvm内存,我们应当知道: 每创建一个线程,jvm就会为其分配一块线程私有的工作内存,其中包括程序计数器、栈,等等。 对于每一个线程私有的栈,当线…

怎么限制文件打开次数、打开时间?

一些公司出于业务需求,可能会给客户或者合作伙伴发一些涉密图纸、文档、文件等重要文件,但是又不想文件被外发泄露随意传播,今天就教大家一个方法限制文件外发后别人打开这个文件的打开次数、打开时间、另存为等操作。 设置方法 本篇文章测试…

page _refcount和_mapcount字段

linux page有两个非常重要的引用计数字段_refcount和_mapcount,都是atomic_t类型,其中,_refcount表示内核中应用该page的次数。当_refcount 0时,表示该page为空闲或者将要被释放。当_refcount > 0,表示该page页面已…

APP-脱壳+反编译

APP反编译加固-自动查壳脱壳 为什么要脱壳? 因为不脱壳无法进行反编译 查壳工具:https://pan.baidu.com/s/1rDfsEvqQwhUmep1UBLUwSQ 密码: wefd 脱壳工具:https://github.com/CodingGay/BlackDex 查壳演示: 使用Java运行jar包&a…

浙江大学软件学院2022保研经历分享

首先,我想强调一点,如果我们只是一个普通的非211,985的本科生,一定要参加浙江大学每年7月份组织的夏令营,因为浙大的夏令营是一个海王营,基本上不会对入营的学生做筛选,但是要想获得优秀营员&am…

pandas笔记:groupby整理

0 数据集 # Visual Python: Data Analysis > File vp_df pd.read_csv(https://raw.githubusercontent.com/visualpython/visualpython/main/visualpython/data/sample_csv/fish.csv) vp_df 1 单列聚合 vp_df.groupby(Type)[Kg].mean()Type mackerel 1.417456 salmon…

【C语言项目】三子棋

文章目录 项目思路一、分文件进行创建二、进入游戏前的目录2.1 目录的功能:2.2 目录界面:2.3 选择进入或退出游戏2.4 多次重玩功能 三、画出棋盘3.1 写出棋子3.2 初始化棋盘3.2 画出棋盘的框架3.3 代码实现 四、玩家落子4.1 落子逻辑4.2具体情况分类讨论…

初识C++(下)——“C++”

各位CSDN的uu们你们好呀,今天终于是小雅兰的初识C的下的文章啦,下面,让我们进入C的世界吧!!! 内联函数 我们首先得知道:C中的内联函数是用来解决C语言中宏的坑的!!&…

jenkins中配置了发送邮件,构建后却没有发邮件Not sent to the following valid addresse

【问题描述】:jekins中配置了发送邮件,构建后却没有发邮件的问题,构建报错:Not sent to the following valid addresse 【报错显示】: 【问题定位】:Extended E-mail Notification中,没有配置…

【Docker】Docker的使用案例以及未来发展、Docker Hub 服务、环境安全的详细讲解

前言 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 📕作者简介:热…

LabVIEW - 串口通信

1. 题目 使用并行程序设计技术,实现串口通信/TCP通信/UDP通信(任选3者一种,其中TCP为客户端)数据发送和数据接收功能在各自的线程中实现,即一个线程只管处理数据发送功能的实现,另一个线程只管数据接收和数…

P2698 [USACO12MAR] Flowerpot S

P2698 [USACO12MAR] Flowerpot S 文章目录 P2698 [USACO12MAR] Flowerpot S题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路分析code [P2698 USACO12MAR] Flowerpot S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 Farmer John has been having …

【漏洞复现】厦门才茂通信网关formping远程命令执行漏洞

文章目录 前言声明一、漏洞描述二、漏洞复现 前言 厦门才茂通信网关formping存在远程命令执行漏洞,攻击者可通过特定payload对目标服务器发起攻击,造成不可估量的影响。 声明 请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提…

文心一言 VS 讯飞星火 VS chatgpt (65)-- 算法导论6.5 4题

四、在 MAX-HEAP-INSERT 的第 2行,为什么我们要先把关键字设为 -∞ ,然后又将其增加到所需的值呢? 文心一言: 在 MAX-HEAP-INSERT 算法的第二行将关键字设为 -∞,是为了保持堆的性质。堆是一种二叉树结构,其中每个节…

python调用文心千帆大模型

一、登录智能云控制台 https://console.bce.baidu.com/ 二、创建AI应用 已登录状态下,进入控制台创建应用 https://console.bce.baidu.com/ai/#/ai/easydlLiteNlp/app/list 应用创建后获取AppID、API Key、Secret Key 三、API调用 import requests import jso…

uniapp项目集成本地插件

在项目根目录下创建nativeplugins文件夹 拷贝插件到目录nativeplugins 在manifest.json -> App原生插件配置 -> 本地插件里勾选插件 删除本地基座和手机app从新自定义基座运行

大白话讲讲 Go 语言的 sync.Map(一)

阅读本文大约需要 4.25 分钟。 程序是枯燥乏味的。 在讲 sync.Map 之前,我们先说说什么是 map(映射)。 我们每个人都有身份证号码,如果我需要从身份证号码查到对应的姓名,用 map 存储是非常合适的。 map[000...001…

[驱动开发]字符设备驱动应用——点灯

点亮开发板stm32mp157的三盏灯 //头文件 #ifndef __LED_H__ #define __LED_H__//封装GPIO寄存器 typedef struct { volatile unsigned int MODER; // 0x00volatile unsigned int OTYPER; // 0x04volatile unsign…