画图理解JVM相关内容

文章目录

    • 1. JVM视角下,内存划分
    • 2. 类内存分布硬核详解
      • 1. 获取堆内存参数
      • 2. 扫描堆内存,定位实例
      • 3. 查看实例所在地址的数据
      • 4. 找到实例所指向的类信息的地址
      • 5. 查看class信息
      • 6. 结论
    • 3. Java的对象创建流程
    • 4. 垃圾判别算法
      • 4.1 引用计数法
      • 4.2 可达性分析算法
    • 5. 垃圾收集算法
      • 5.1 标记-清除算法
      • 5.2 标记-复制算法
      • 5.3 标记-整理算法

1. JVM视角下,内存划分

在这里插入图片描述

tip: 额外补充

  • 在以“分代设计”为主导的堆内存,其控件划分大致如上图所示。但G1垃圾回收期为分解,后续的内存设计并没有都参考分代理论,因此jdk8以后(G1大规模运用在jdk8之后),内存划分有待商榷
  • 堆虽然是线程共享的,但他可以为线程划分缓冲区——Thread Local Allocation Buffer,TLAB。TLAB是线程私有的。但无论怎么划分,堆都是存储对象实例
  • 直接内存:属于操作系统本地内存,不归JVM管理。因此GC对他无效

2. 类内存分布硬核详解

既然是硬核,不来点内存轰炸是对不起硬核两字。

下文主要讲述一个类在创建过程中,可能会涉及到的所有类在内存的分布情况。包括JVM层面的instanceKlass,Java层面的Test实例,Test.class

下文内容比较硬核,请读者酌情阅读。另外,底层指针分析可能存在纰漏,欢迎读者友善指出

让我们开始!


demo代码如下

package com.xhf.test;// -XX:+UseSerialGC -Xmn10M -XX:-UseCompressedOops
public class TestDemo {public static void main(String[] args) {new Test();while (true) {}}
}
package com.xhf.test;public class Test {private static Integer a;private Integer b;private int c;public int d;private void func() {}public void func2() {}
}

1. 获取堆内存参数

打开HSDB,扫描堆的整体内存范围 universe

Heap Parameters:
Gen 0:   eden [0x0000000080000000,0x00000000803845a8,0x0000000080800000) space capacity = 8388608, 43.96257400512695 usedfrom [0x0000000080800000,0x0000000080800000,0x0000000080900000) space capacity = 1048576, 0.0 usedto   [0x0000000080900000,0x0000000080900000,0x0000000080a00000) space capacity = 1048576, 0.0 usedInvocations: 0Gen 1:   old  [0x0000000080a00000,0x0000000080a00000,0x000000008fe00000) space capacity = 255852544, 0.0 usedInvocations: 0

其它信息我们可以不用关注,只需要知道,eden区的范围是0x0000000080000000 0x0000000080800000,绝大多数情况下,对象的空间有限划分在eden区域。因此,我们想要探查Test示例相关内存地址,需要扫描eden区域

2. 扫描堆内存,定位实例

scanoops 0x0000000080000000 0x0000000080800000 com.xhf.test.Test

hsdb> scanoops 0x0000000080000000 0x0000000080800000 com.xhf.test.Test
0x000000008023e2d0 com/xhf/test/Test

主程序运行new Test();,他的实例对象被划分在0x000000008023e2d0地址

3. 查看实例所在地址的数据

inspect 0x000000008023e2d0

hsdb> inspect 0x000000008023e2d0
instance of Oop for com/xhf/test/Test @ 0x000000008023e2d0 @ 0x000000008023e2d0 (size = 32)
_mark: 1
_metadata._klass: InstanceKlass for com/xhf/test/Test
b: null null
c: 0
d: 0

在控制台上通过指令,查看不到最全面的信息,通过Tools->inspector创建可视化窗口,可以查看最全面的信息,具体如下
在这里插入图片描述

在这里插入图片描述
通过上述两幅图,我们可以返现很多有趣的细节

  1. _mark字段,mark其实就是markword,对象头的意思。markword能够存储相当丰富的信息,比如分代年龄,gc次数,偏向锁,重锁等等信息。
  2. _metadata._klass,类型指针,指向类型com.xhf.test.Test.class。该字段用于表示当前实例是哪个类的实例
  3. b, c, d:3个字段属于oop,但a不属于oop,a属于Test.class,因为他是静态变量。此外,b这个Object被赋值null,c,d两个基本int类型赋值为0

4. 找到实例所指向的类信息的地址

我们找到Test oop,但没有找到存储Test类信息的数据地址。inspect无法直接看到_metadata._klass指向的地址,我们通过内存扫描,直接查看内存数据

mem 0x000000008023e2d0 2 :查看0x000000008023e2d0地址,偏移2个单位(8bit)

hsdb> mem 0x000000008023e2d0 2
0x000000008023e2d0: 0x0000000000000001 
0x000000008023e2d8: 0x0000000013ff3400 

0x0000000013ff3400,就是oop指向的Test类信息所在地址

注意,笔者这里并没有说明0x0000000013ff3400是Test.class类对象的地址

5. 查看class信息

如下图所示,0x0000000013ff3400才是class真正的信息,这也被称为元信息,被JVM存储在meta space中

在这里插入图片描述
!!!需要注意的是,0x0000000013ff3400地址上的内容不是Java意义上的Test.class这个类

笔者为什么会这么说呢?原因是JVM内部采用C++的instanceKlass描述
Java类,并且会将instanceKlass分配到meta space

而instanceKlass有个叫做_java_mirror的字段,它指向的才是Java类的Class对象

在这里插入图片描述
本例中就是Test.class这个对象

我们监视这个地址inspect 0x000000008023e210

hsdb> inspect 0x000000008023e210
instance of Oop for java/lang/Class @ 0x000000008023e210 @ 0x000000008023e210 (size = 168)
a: null null

发现_java_mirror指向的对象,是java/lang/Class类(Test.class),并且大小168bit

我们扫描0x000000008023e210往后的168bit(21个8bit)内存空间

mem 0x000000008023e210 21

hsdb> mem 0x000000008023e210 21
0x000000008023e210: 0x0000000000000001 
0x000000008023e218: 0x0000000013c03ed0 
0x000000008023e220: 0x0000000000000000 
0x000000008023e228: 0x0000000000000000 
0x000000008023e230: 0x0000000000000000 
0x000000008023e238: 0x00000000800dba38 
0x000000008023e240: 0x0000000000000000 
0x000000008023e248: 0x0000000000000000 
0x000000008023e250: 0x0000000000000000 
0x000000008023e258: 0x0000000000000000 
0x000000008023e260: 0x0000000000000000 
0x000000008023e268: 0x0000000000000000 
0x000000008023e270: 0x0000000000000000 
0x000000008023e278: 0x0000000080239560 
0x000000008023e280: 0x0000000000000000 
0x000000008023e288: 0x0000000000000000 
0x000000008023e290: 0x0000000013ff3400 
0x000000008023e298: 0x0000000000000000 
0x000000008023e2a0: 0x0000001500000000 
0x000000008023e2a8: 0x0000000000000001 
0x000000008023e2b0: 0x0000000000000000 

发现内存地址为0x000000008023e290时,存放的数据是:0x0000000013ff3400

0x0000000013ff3400的内容,恰好是instanceKlass所在地址。

6. 结论

基于上述分析,我们得出如下结论:

Test实例 -> Test instanceKlass <-> Test.class

文字枯燥乏味,看图就好理解了

在这里插入图片描述

3. Java的对象创建流程

有了第2节的基础,第三节的分析自然就简单多了。

具体流程直接上图
请添加图片描述

这个流程中,具体的内存情况如下
请添加图片描述

tip:
严格来说,上图存在一定的问题。
由第2节可知,实例的指针指向的是instanceKlass,而非class对象。这里这么处理是为了方便画图。
而且,instanceKlass拥有class对象的指针,实例可以通过instanceKlass找到class对象,只是需要两次指针跳跃,所以上图绘制方式其实也并无太大问题

4. 垃圾判别算法

4.1 引用计数法

给对象增加计数器,当计数器为0,表示对象不再被引用。可以当作垃圾被垃圾清除器清理

这种算法的缺陷很明显,一方面开销大,JVM需要维护所有对象的引用计数器;另一方面,无法解决循环引用的问题

4.2 可达性分析算法

以GC Root根节点的集合,作为起始点。按照对象之间的引用关系向下遍历,如果某个对象无法和GC Root关联,那么我们认为该对象是不可达的,可以当作垃圾被回收

在这里插入图片描述

5. 垃圾收集算法

在讲解回收算法前,我们需要补充一些分代理论的基础知识

  • 大部分对象都是朝生幕死,创建出来很快就被回收
  • 如果一个对象经历了多次垃圾回收,那么该对象可以被认为是长时间存活的对象

曾经有个组织做过调查,98%的对象活不过一轮垃圾回收

考虑到对象存活时间长短存在差异,我们可以大致将堆内存划分为两块空间

  • 新生代(Young Generation)
  • 老年代(Old Generation)

新生代存放寿命短的对象;老年代存放长命的对象。这样在做垃圾回收时,可以根据不同区域对象存活特点做出不一样的垃圾回收策略,以此提高运行效率

5.1 标记-清除算法

标记清楚算法是最基础的垃圾回收算法,后续的算法基本都是在此基础上进行改进。

该算法的核心是

  • 标记垃圾(可达性分析算法)
  • 清除垃圾

在这里插入图片描述
标记-清除算法执行流程如上图所示

上述算法存在以下两个缺陷

  • 算法效率不稳定:如果内存中存在大量需要清除的垃圾,JVM需要执行多次的清除操作;反之,如果垃圾数量较少,JVM执行清除操作次数就少
  • 空间碎片:当JVM执行清除操作后,会存在大量内存碎片,内存中使用的空间不连续。这极大的降低了内存利用率,提高了内存申请的难度

5.2 标记-复制算法

标记-复制算法,将内存划分为等大的两个空间,一个空间用于存放对象,另一个空间用于预留。

当需要进行内存清除时,操作异常容易,因为两个区间在同一时刻只有一个区间存在使用的对象,因此只需要将存放对象的空间中,存活的对象复制到预留空间,然后清除原有空间的所有内容,即可完成垃圾回收

在这里插入图片描述

该算法让JVM只需要关注存活的对象,如果存活对象少,那么复制操作少,效率高,因此标记-复制算法一般用于Eden区域的垃圾回收。此外,该算法成功解决了内存碎片的问题

但显而易见,该算法带来了另一个问题

  • 内存利用率低:该算法需要额外的空间进行存储,比标记清除算法大了1倍的空间

5.3 标记-整理算法

该算法就是在标记-清除的基础上,增加了整理的操作。对于清除后的内存空间,该算法会通过移动已使用的空间,让内存的使用再次连续

在这里插入图片描述
该算法解决了内存碎片问题,但移动存活对象这个操作引入了新的问题。就比如原先对象A引用了对象B,现在B的地址修改了,A如何感知到。此外,在移动过程中,需要暂停用户线程(Stop the world),因此需要移动的对象数量要尽可能少,以此减少stop the world的时间

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

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

相关文章

【Redis】NoSQL之Redis的配置和优化

关系型数据库与非关系型数据库 关系型数据库 关系型数据库是一个结构化的数据库&#xff0c;创建在关系型模型&#xff08;二维表&#xff09;的基础上&#xff1b;一般面向于记录&#xff1b; SQL语句(标准数据查询语句)就是一种基于关系型数据库的语言&#xff0c;用于执行…

Mysql底层原理五:如何设计、用好索引

1.索引的代价 空间上的代价 时间上的代价 每次对表中的数据进⾏增、删、改操作时&#xff0c;都需要去修改各个B树索引。⽽且我们讲过&#xff0c;B树每层节点都是按照索引列的值从⼩到⼤的顺序排序⽽组成了双 向链表。不论是叶⼦节点中的记录&#xff0c;还是内节点中的记录&a…

设计模式 -- 发布订阅模式

发布订阅模式&#xff1a; 订阅者把自己想订阅的事件注册到调度中心&#xff0c;当发布者发布该事件到调度中心&#xff0c;也就是该事件触发时&#xff0c;由调度者统一调度订阅者注册到调度中心的处理代码。 在javaScript 中我们一般使用事件模型来代替传统的发布订阅模式。 …

深入了解iOS内存(WWDC 2018)笔记-内存诊断

主要记录下用于分析iOS/macOS 内存问题的笔记。 主要分析命令&#xff1a; vmmap, leaks, malloc_history 一&#xff1a;前言 有 3 种思考方式 你想看到对象的创建吗&#xff1f;你想要查看内存中引用对象或地址的内容吗&#xff1f;或者你只是想看看 一个实例有多大&#…

互联网大厂ssp面经之路:计算机网络part2

什么是 HTTP 和 HTTPS&#xff1f;它们之间有什么区别&#xff1f; a. HTTP&#xff08;超文本传输协议&#xff09;和HTTPS&#xff08;安全超文本传输协议&#xff09;是用于在Web上传输数据的协议。它们之间的区别在于安全性和数据传输方式。 b. HTTP是一种不安全的协议&…

【随笔】Git 高级篇 -- 整理提交记录(上)cherry-pick(十五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

加州大学欧文分校英语基础语法专项课程03:Simple Past Tense 学习笔记(完结)

Learn English: Beginning Grammar Specialization Specialization Certificate course 3&#xff1a; Simple Past Tense Course Certificate 本文是学习 https://www.coursera.org/learn/simple-past-tense 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。…

浙江大学李春阳团队Trends Plant Sci观点文章(IF=20):植物地下生态互作:为什么同性相斥,异性相吸?

在生态学中&#xff0c;人们一直致力于探究生物之间的相互作用&#xff0c;这些相互作用不仅包括物种之间的相互作用&#xff0c;还包括同一物种的不同性别之间的相互作用。对于异株植物物种来说&#xff0c;人们普遍认为异性之间的相互作用比同性之间的相互作用更弱&#xff0…

为说阿拉伯语的国家进行游戏本地化

阿拉伯语是由超过4亿人使用的语言&#xff0c;并且是二十多个国家的官方语言。进入这些国家的市场并非易事——虽然他们共享一种通用语言&#xff0c;但每个国家都有自己独特的文化&#xff0c;有自己的禁忌和对审查的处理方式。这就是为什么视频游戏公司长期以来都远离阿拉伯语…

Git如何将已经推送到服务器的文件夹“忽略”

例子&#xff1a;如果我们在推送之初&#xff0c;一股脑将工程的所有文件都备份&#xff0c;没有忽略 debug和release文件夹&#xff0c;反应过来想要将文件夹再次忽略&#xff0c;应该怎么操作呢&#xff1f; 如下解答方法&#xff1a; 1.在工程目录下新建文件 .gitignore …

graphicLayer.startDraw({指定type为curve曲线时,无法实现示例效果排查思路参考

graphicLayer.startDraw({指定type为curve曲线时&#xff0c;无法实现和示例一样的曲线效果的排查思路参考&#xff1a; 相关代码&#xff1a; graphicLayer.startDraw({type: "curve",style: {color: "#ff0000",width: 3,},}); 相关效果&#xff1a; …

创建型模式--4.抽象工厂模式【弗兰奇一家】

1. 奔向大海 在海贼世界中&#xff0c;位于水之都的弗兰奇一家是由铁人弗兰奇所领导的以拆船为职业的家族&#xff0c;当然了他们的逆向工程做的也很好&#xff0c;会拆船必然会造船。船是海贼们出海所必备的海上交通工具&#xff0c;它由很多的零件组成&#xff0c;从宏观上看…

Mathpix和Simpletex对比

原始资料 Mathpix结果 已知集合 A { y ∣ y 2 x } , B { x ∣ x ≥ a } A\left\{y \mid y2^{\sqrt{x}}\right\}, B\{x \mid x \geq a\} A{y∣y2x ​},B{x∣x≥a}, 若 A B AB AB, 则 a a a 的值为 ( ) A. 1 B. 2 C. 3 D. 4复数 z a i ( a ∈ R , i za\mathrm{i} \qua…

React - 你知道useffect函数内如何模拟生命周期吗

难度级别:中级及以上 提问概率:65% 很多前端开发人员习惯了Vue或者React的组件式开发,熟知组件的周期过程包含初始化、挂载完成、修改和卸载等阶段。但是当使用Hooks做业务开发的时候,看见一个个useEffect函数,却显得有些迷茫,因为在us…

windows安装使用nacos

1.下载安装包 网址&#xff1a;Releases alibaba/nacos GitHub 2.解压&#xff0c;bin目录下修改启动脚本为单机 3.修改数据库配置&#xff0c;使用本地mysql数据库 3.1 创建nacos数据库 3.2 执行 nacos\conf 目录下数据库脚本 4.修改nacos\conf目录下数据库配置 5.点击运…

【数据结构】考研真题攻克与重点知识点剖析 - 第 5 篇:树与二叉树

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

Javascript进阶内容

1. 作用域 1.1 局部作用域 局部作用域分为函数作用域 和 块级作用域 块级作用域就是用 {} 包起来的&#xff0c;let、const声明的变量就是产生块作用域&#xff0c;var不会&#xff1b;不同代码块之间的变量无法互相访问&#xff0c;里面的变量外部无法访问 1.2 全局作用域…

【图论】Leetcode 994. 腐烂的橘子【中等】

腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到单…

Redis在windows中安装启动停止

Redis下载 Redis安装 解压即可 启动 停止 ctrlc 启动客户机 设置密码 打开redis.windows.conf Spring Data Redis 使用方式 导入spring Data Redis 的maven坐标 配置Redis数据源 3编写编写配置类&#xff0c;创建RedisTemplate对象

day75 js 正则表达式 window对象轮播图片调用定时器

一 正则表达式: RegExp 对象: 对字符串执行模式匹配的强大工具。 1 创建正则表达式对象 let reg /模式/修饰符 修饰符 attributes 是一个可选的字符串&#xff0c;包含属性 "g"、"i" 和 "m"&#xff0c; …