JVM 内存和 GC 算法

文章目录

  • 内存布局
  • 直接内存
  • 执行引擎
    • 解释器
    • JIT 即时编译器
      • JIT 分类
      • AOT 静态提前编译器(Ahead Of Time Compiler)
  • GC
    • 什么是垃圾
    • 为什么要GC
    • 垃圾回收行为
    • Java GC 主要关注的区域
    • 对象的 finalization 机制
    • GC 相关算法
      • 引用计数算法(Reference Counting)
      • 可达性分析算法
          • GC Roots
      • Stop The World
      • 标记-清除算法(mark-sweep)
      • 复制算法(Copying)
      • 标记-压缩(标记-整理)算法
    • 分代垃圾回收
    • 增量收集算法
    • 分区算法

内存布局

  • 对象头(Header)
    • 运行时元数据(Mark word):哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
    • 类型指针 :指向类元数据,确定对象所属类型。如果是数组还要记录数组的长度
  • 实例数据(Instance Data):类中的各类型变量以及父类的相关类型数据。
  • 对齐填充(Padding):非必须,也没有特殊含义,仅起到占位符的作用

直接内存

  • 直接内存不是JVM 运行时数据区的一部分,是 Java 堆外的,系统的内存区间。NIO 通过存在堆中的 DirectByteBuffer 操作 Native 内存。通常情况下,直接内存的运行效率优于 Java 堆,读写频繁的场合,如果NIO 库就允许 Java 程序使用直接内存。

直接内存在 Java 堆外,因此是不受 -Xmx 的限制的,但操作系统内存是有限的

  • -XX:MaxDirectMemorySize=1G,表示设置 NIO 可以操作的直接内存最大大小为 1G,默认为 0,表示 JVM 自动选择 NIO 可以操作的直接内存大小。

直接内存也会 OOM。

执行引擎

执行引擎(Execution Engine),将字节码指令解释/编译(注意与 Java 文件编译为 .class 文件的编译区分,有的地方称之为后端编译)为对应平台上的本地机器指令,实际就是将高级编程语言翻译为机器指令。

解释器

当 Java 虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件内容“翻译”为对应平台的本地机器指令。此过程由解释器执行。

  • 字节码解释器:纯软件代码模拟字节码执行,效率低下。
  • 模板解释器:每一条字节码和一个模板函数相关联,模板函数直接产生这条字节码执行时的机器码,效率高。

基于解释器来执行,还是相对低效的,所以又有了 JIT 即时编译器。

JIT 即时编译器

JIT(Just In Time Compiler)即时编译器:就是 JVM 将源代码直接编译成和本地机器相关的机器语言。

  • 根据代码被调用的执行频率来确定是否需要启动 JIT 来进行编译,这些需要被 JIT 编译为本地指令的代码,称为“热点代码”,JIT 在运行时会针对这些热点代码做深度优化,以提高 Java 程序的性能。
  • 栈上替换:一个多次被调用的方法,或者一个方法体内部的循环次数较多的循环体,都可以称为“热点代码”,他们都可以通过 JIT 编译为本地机器指令。由于此过程发生在方法执行过程中,因此也称为栈上替换(OSR 编译)On Stack Replacement。
  • 热点探测功能:HotSpot 基于计数器的方式来进行热点探测热点代码被调用的次数。Client 模式 1500次,Server 模式 10000 次,才是热点代码,才切换为 JIT 编译。
  • 指令缓存:JIT 编译为本机指令代码后,会进行代码缓存,以提高性能。

此外我们还可以通过 java 命令设置,让程序使用纯解释器或纯 JIT 编译器执行,或者两者的混合执行的模式。

  • -Xint 纯解释器模式
  • -Xcomp 纯编译器模式
  • -Xmixed 混合模式(默认)

JIT 分类

JIT 分为如下两类编译器:

  • C1(Client Compiler)编译器:运行在 -client 模式下,C1 编译器会对字节码进行简单和可靠的优化,耗时短。
    • 方法内联
    • 去虚拟化
    • 冗余消除
  • C2(Server Compiler)编译器:运行在 -server 模式下,C2 进行耗时较长的优化,以及激进优化。但优化后的代码执行效率高。
    • 标量替换
    • 栈上分配
    • 同步消除
  • Graal 编译器:JDK 10+ 才加入的全新的即时编译器。

AOT 静态提前编译器(Ahead Of Time Compiler)

JIT 是程序运行中执行的,AOT 编译是在程序运行之前执行,将字节码转换为机器码的过程。好处:预编译.class 文件为 .os 文件

GC

什么是垃圾

垃圾是指在运行程序中没有任何指针指向的对象。如果不及时堆垃圾进行清理,这些垃圾会一直占用内存空间,直到程序运行结束,在这期间这些对象所占用的内存无法使用,造成极大的资源浪费。

为什么要GC

如果不 GC ,内存迟早会消耗完,导致新的对象无法分配内存,所以没有 GC 程序就可能无法正常执行。

垃圾回收行为

  • 手动GC:C / C++ 需要开发人员手动进行内存的申请和回收,这样做的好处是灵活,但坏处是操作太频繁,而且对开发人员的要求较高,相对增加了开发人员的负担。
  • 自动GC:Java 采用的是自动 GC 的机制。自动管理内存,无需开发人员手动分配和回收。坏处是弱化了开发人员对内存的管理,只能通过监控和调节相关参数来优化。

Java GC 主要关注的区域

  • 方法区(注:方法区对应永久代或元空间,很多 JVM 没有方法区的 GC)
  • 堆区

Java GC 的主要特点为:频繁收集 Young 区(年轻代),较少收集 Old 区(老年代),基本不收集 Perm 区(老年代、元空间)

对象的 finalization 机制

当垃圾回收器对垃圾对象进行垃圾回收之前,会先调用该对象的 finalize 方法,该方法在 Object 类中定义,可以被重写,主要用于在对象被回收时进行资源释放。我们通常在此方法中进行一些资源释放的操作和清理的操作,比如:File 、IO 操作的关闭、Socket 操作的关闭、数据库连接的关闭等等。

注:不要在程序中主动调用 finalize 方法,该方法仅提供给垃圾回收器调用

  • 在 finalize 时,可能导致对象复活
  • finalize 方法执行时间是没有保障的,它有 GC 线程决定,若不发生 GC,则不会执行。GC 时调用 finalize 方法是由单独的优先级较低一点的线程(Finalizer)来执行。执行前放在执行队列中(因为 GC 是有多个对象的 finalize 方法需要调用)
  • finalize 方法还可能导致 GC 失败,所以在重写该方法时要注意执行效率等。

由于 finalize 方法,对象可能会出现如下几个状态:

  • 可触及:该对象可达。(可达性算法分析)
  • 可复活:对象不可达,但对象可能调用 finalize 方法后复活。(在 finalize 方法中使当前对象跟引用链中任何一个对象建立联系,就会导致对象复活。但之后再次不可用,则不会调用 finalize 方法了。finalize 方法只调用一次)
  • 不可触及:finalize 方法被调用成功,且对象没有复活。只有此状态的对象才可不垃圾回收。

GC 相关算法

GC 分为两个阶段:标记阶段和清除阶段,每个阶段都有对应的算法,这些算法可以统称为垃圾回收算法。

  • 标记阶段:判断对象是否存活。其对应的算法有引用计数算法和可达性分析算法
  • 清除阶段:在判断对象释放存活之后,GC 接下来就会对死亡的对象进行垃圾回收,释放内存空间。目前常用的算法有,标记-清除算法(mark-sweep)、复制算法(Copying)、标记-压缩算法(mark-Compact)

引用计数算法(Reference Counting)

每个对象保存一个整型引用计数器,记录该对象被引用的情况。只要有任何一个地方引用了该对象,则该对象的计数器值 +1 ,如果不再引用了,则计数器值 -1,只要该对象的引用计数器的值为 0,则表示该对象死亡,可以被 GC 回收。

  • 优点:实现简单,垃圾对象判断简单,判断效率高,回收没有延迟性。
  • 缺点
    • 需要独立的字段存储计数器,增加内存开销
    • 任何引用的变动都需要更新计数器的值
    • 无法处理循环引用(这是个致命的问题,所以目前的 JVM 中都没有使用此算法了)
      在这里插入图片描述

可达性分析算法

可达性分析算法又叫根搜索算法或跟踪性垃圾收集,相对引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点而且可以有效的解决循环引用问题,防止内存泄漏的问题发生。Java 就是选择的可达性分析算法。

  • 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达。
  • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索走过的路径称为引用链(Reference Chain)
  • 如果目标对象没有任何引用链相连,则对象释不可达的,可以标记为垃圾对象。只有直接或间接被根对象连接的对象才是存活对象。
GC Roots

GC Roots 包含如下几类元素:

  • JVM 中引用的对象
    • 如:各个线程被调用的方法中使用的参数、局部变量等。
  • 本地方法栈内 JNI (本地方法)引用的对象
  • 方法区中静态属性引用的对象
    • 如:对象类型的静态变量
  • 方法区中常量引用的对象
    • 如:字符串常量池中的引用对象
  • 被同步锁 synchronized 持有的对象
  • JVM 内部的引用
    • 如:系统类加载器、Class 对象等
  • 反应 JVM 内部情况的 JMXBean 、JVMTI 中的注册回调、本地代码缓存等。

Stop The World

如果要使用可达性分析算法来判断内存是否可以回收,那么分析工作必须在一个能保障一致性的快照中进行,否则分析结果无法保证完全正确。所以在 JVM 在进行 GC 时,必须 “Stop The World” 用户线程出现停顿。

标记-清除算法(mark-sweep)

标记-清除算法就分为标记和清除两个阶段:

  • 标记:收集器从根节点开始遍历,标记所有被引用的对象(注意:这里是标记的可用对象,标记的内容标记在对象头 Header 中)
  • 清除:收集器从堆内存从头到尾的线性遍历,如果发现某个对象没有标记为可用对象,则将其回收。

标记-清除算法简单明了,但在进行 GC 的时候需要停止整个应用程序,而且清理出来的内存空间是不连续的,容易产生内存碎。可能导致可用空间碎片化,不能整体存放大对象。而且该算法需要经历标记-清除两步,意味着需要两次遍历对象。

在这里插入图片描述

标记-清除算法的清除并不是真的清除,只是将可回收的对象的地址进行记录(放在空闲列表),当有新对象来的时候,进行覆盖。

复制算法(Copying)

将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,然后交换两个内存块的角色,最后完成垃圾回收。(这里是不是想起了什么?我们的两个幸存者区就是使用的该方式)

在这里插入图片描述
优点:一次遍历,更高效,复制后内存连续,没有碎片化问题。
缺点:需要两份内存空间,且任何时刻都有一个空间不使用,而且对象需要复制,当存活对象很多的情况下,复制所占用的系统资源也不少。对于存活对象不多的情况比较适用。(想想为什么在年轻代中的幸存者区使用该算法)

标记-压缩(标记-整理)算法

在年轻代中,一般只有少部分对象存活,使用复制算法,可以有效利用复制算法的优点,但在老年代中,存活对象更多,采用复制算法就会放大其缺点,所以 JVM 的开发者,在标记-清除算法的基础上改进,形成了标记-压缩算法。

标记-压缩算法也是分为两步:

  1. 第一阶段和标记-清除算法一样,标记所有被引用的对象。
  2. 将所有存活的对象压缩到内存的一端,按顺序排放,然后清理空间。
    在这里插入图片描述
  • 优点:解决了标记-清除算法的碎片化问题,消除了复制算法的内存减半的代价。
  • 缺点:效率上来说还是低于复制算法,甚至低于标记-清除算法。对比标记-清除算法,标记-压缩算法有对象的移动,对应的引用地址就会涉及修改等。

标记-压缩算法的开销

  • 标记(mark)阶段的开销与存活对象的数量成正比。
  • 清除(sweep)阶段的开销与所管理的区域大小成正比。
  • 压缩(Compact)阶段的开销与存活对象的数量成正比

分代垃圾回收

  • 年轻代:区域相对老年代较小,对象生命周期短,存活率低,回收频繁。基于此特点,采用了复制算法进行垃圾回收,速度快,效率高,而且年轻代中两个幸存者区就是为了利用复制算法来划分的。
  • 老年代:区域相对较大,对象存活率高,生命周期较长,回收不及年轻代频繁。基于此特点,采用的是标记-清除算法和标记-压缩算法混合使用的方式实现的垃圾回收。

增量收集算法

标记-清除、标记-压缩、复制算法,都或多或少的会有 stop the world 的出现,如果垃圾收集时间过长,应用程序会被挂起时间过长,影响用户体验。基于此情况,又诞生了增量收集算法。

如果一次性进行垃圾收集,将所有的垃圾进行处理,可能导致时间过长,所以增量收集算法就采用了垃圾收集线程和应用程序线程交替执行的思想,垃圾收集线程每次执行只收集一小片区域的内存空间,然后切换到应用程序线程执行,反复执行直到垃圾收集完成。(其本质上还是我们上面说到的垃圾收集算法,只是将垃圾收集和应用程序线程交替执行,以减少 stop the world 的时间)

  • 优点:减少了 stop the world 的时间
  • 缺点:交替执行线程,因为线程和线程上下文的切换的消耗,会使得垃圾回收的总成本上升,造成系统吞吐量下降。

分区算法

分区算法将堆空间划分为更小的区间,分代算法按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同区域。每一个区域都独立使用,独立回收。这样一次回收多个小空间,降低了 stop the World 的时间。

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

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

相关文章

Proteus仿真--1602LCD显示仿手机键盘按键字符(仿真文件+程序)

本文主要介绍基于51单片机的1602LCD显示仿手机键盘按键字符(完整仿真源文件及代码见文末链接) 仿真图如下 其中左下角12个按键模拟仿真手机键盘,使用方法同手机键一样,长按自动跳动切换键值,松手后确认选择&#xff…

JavaScript_Date对象_实例方法_get类

计算这一年还剩多少天&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document&…

《Pytorch新手入门》第二节-动手搭建神经网络

《Pytorch新手入门》第二节-动手搭建神经网络 一、神经网络介绍二、使用torch.nn搭建神经网络2.1 定义网络2.2 torch.autograd.Variable2.3 损失函数与反向传播2.4 优化器torch.optim 三、实战-实现图像分类(CIFAR-10数据集)3.1 CIFAR-10数据集加载与预处理3.2 定义网络结构3.3…

通过环境变量实现多个JDK切换

前文: 由于jdk版本需要升级为jdk17,因为jdk8比较常用且稳定,本人又不想卸载掉安装的jdk8,在经过查找资料后找到了可以通过修改环境变量在本地任意切换jdk版本 环境变量配置 网上教程一堆,直接跳过了,这里主要说明怎么通过配置环境变量切换 电脑->属性->高级系统设置-&g…

latex自定义缩写

Latex 写文章可能常用到一些缩写&#xff0c;如&#xff1a; .e.g.i.e.cf.etc.w.r.t.i.i.d.et al. 其中有些要斜体&#xff0c;如果每次都要用 \textit{...}、{\it ...} 弄斜&#xff0c;有点麻烦。CVPR 模板中有定义一些命令&#xff0c;可以更方便地输入这些缩写。这里记录…

【Kotlin精简】第7章 泛型

1 泛型 泛型即 “参数化类型”&#xff0c;将类型参数化&#xff0c;可以用在类&#xff0c;接口&#xff0c;函数上。与 Java 一样&#xff0c;Kotlin 也提供泛型&#xff0c;为类型安全提供保证&#xff0c;消除类型强转的烦恼。 1.1 泛型优点 类型安全&#xff1a;通用允许…

【iOS】——知乎日报第三周总结

文章目录 一、获取新闻额外信息二、工具栏按钮的布局三、评论区文字高度四、评论区长评论和短评论的数目显示五、评论区的cell布局问题和评论消息的判断 一、获取新闻额外信息 新闻额外信息的URL需要通过当前新闻的id来获取&#xff0c;所以我将所有的新闻放到一个数组中&…

android studio 字节码查看工具jclasslib bytecode viewer

jclasslib bytecode viewer 是一款非常好用的.class文件查看工具&#xff1b; jclasslib bytecode editor is a tool that visualizes all aspects of compiled Java class files and the contained bytecode. Many aspects of class files can be edited in the UI. In addit…

快速排序(Java)

基本思想 快速排序Quicksort&#xff09;是对冒泡排序的一种改进。 基本思想是分治的思想&#xff1a;通过一趟排序将要排序的数据分割成独立的两部分&#xff0c;其中一部分的所有数据都比另外一部分的所有数据都要小&#xff0c;然后再按此方法对这两部分数据分别进行快速排…

嵌入式中如何把C++代码改写成C语言代码

由于C&#xff0b;&#xff0b;解释器比C语言解释器占用的存储空间要大500k左右。为了节省有限的存储空间、降低成本&#xff0c;同时也为了提高效率&#xff0c;将用C&#xff0b;&#xff0b;语言写的源程序用C语言改写是很有必要的。 C&#xff0b;&#xff0b;与C最大的区…

Redis-使用java代码操作Redis->java连接上redis,java操作redis的常见类型数据存储,redis中的项目应用

java连接上redisjava操作redis的常见类型数据存储redis中的项目应用 1.java连接上redis package com.zlj.ssm.redis;import redis.clients.jedis.Jedis;/*** author zlj* create 2023-11-03 19:27*/ public class Demo1 {public static void main(String[] args) { // …

【JavaSE】基础笔记 - 类和对象(上)

目录 1、面向对象的初步认知 1.1、什么是面向对象 1.2、面向对象与面向过程 2. 类定义和使用 2.1、简单认识类 2.2、类的定义格式 2.3、自定义类举例说明 2.3.1、定义一个狗类 2.3.2、定义一个学生类 3、类的实例化 3.1、什么是实例化 3.2、类和对象的说明 1、面向…

非线性【SVM】的创建和使用

先来绘制散点图&#xff1a; from sklearn.datasets import make_circles X,y make_circles(100, factor0.1, noise.1) # 100个样本&#xff0c;factor:内圈和外圈的距离之比&#xff0c;noise:噪声 X.shape y.shape plt.scatter(X[:,0],X[:,1],cy,s50,cmap"rainbow&qu…

园区网真实详细配置大全案例

实现要求&#xff1a; 1、只允许行政部电脑对全网telnet管理 2、所有dhcp都在核心 3、wifi用户只能上外网&#xff0c;不能访问局域网其它电脑 4、所有交换机上开rstp协议&#xff0c;接入交换机上都开bpdu保护&#xff0c;核心lsw1设置为根桥 5、只允许vlan 10-40上网 5、所有…

MFC 窗体插入图片

1.制作BMP图像1.bmp 放到res文件夹下&#xff0c;资源视图界面导入res文件夹下的1.bmp 2.添加控件 控件类型修改为Bitmap 图像&#xff0c;选择IDB_BITMAP1 3.效果

【触想智能】工业显示器上市前的检测项目分享

工业显示器在上市前&#xff0c;需要做一项重要的工作&#xff0c;那就是工业显示器出厂前的产品可靠性检测。 工业显示器选择的测试项目相比商用端更为严格&#xff0c;常见的性能测试项目包括高温老化、防尘防水、电磁静电干扰、防摔防撞等&#xff0c;在工业级应用领域&…

学习Opencv(蝴蝶书/C++)相关——1. 前言 和 第1章.概述

文章目录 1. 整体架构1.1 OpenCV3.01.2 Opencv4.xX. 在线文档X.1 Opencv cheatsheet(小抄)1. 整体架构 1.1 OpenCV3.0 对于Opencv3.x版本,网上最常见的图,图自OpenCV Tutorial-Itseez 现在已经不是500+的算法了,而是2500+,详见:About

CANoe新建XML自动化Test Modules

文章目录 1.打开Test Modules2.新建Environment3.新建XML Test Modules4.新建.can文件5.打开XML Test Modules6.新建xml脚本并保存7.编译8.在.can文件写个测试用例9.修改报告格式为HTML10.运行查看报告后面介绍的文章会重复用到这部分,这里单独介绍下,后面不做重复介绍。 1.…

springboot中使用redis管理session

前言 使用软件&#xff1a; redis&#xff1a; linux版本下载 windows版本下载 安装redis 下载redis http://download.redis.io/releases/ 源码安装redis&#xff08;ubuntu&#xff09; #将指定版本的redis上传到服务器#解压 sudo tar -xzvf redis-6.2.4.tar.gzcd re…

python创建一个简单的flask应用

下面用python在本地和服务器上分别创建一个简单的flask应用&#xff1a; 1.在pc本地 1&#xff09;pip flask后创建一个简单的脚本flask_demo.py from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return Hello, World!winR进入命令行&#xff0c;…