认识JVM

这篇具有很好参考价值的文章主要介绍了认识JVM。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

✏️作者:银河罐头
📋系列专栏:JavaEE

🌲“种一棵树最好的时间是十年前,其次是现在”


推荐一本书:《深入理解 Java 虚拟机》。

JVM(Java Virtual Machine)内存区域划分,JVM 类加载机制,JVM 垃圾回收机制。

HotSpot VM : 最主流的 JVM,Oracle 官方 jdk 和开源的 openjdk ,都是使用这个 JVM。占据绝大部分市场份额。

JVM 内存区域划分

举个栗子:买房子,假设买了一套 100 平米的房子,区域划分,主卧,次卧,客厅,阳台,厨房,卫生间,浴室…

JVM 启动的时候,也会申请到一块很大的内存区域,JVM 是一个应用程序,要从操作系统申请内存。JVM 就会根据内存需要,把空间划分成几个部分,每个部分有各自的功能和作用。

认识JVM

此处所说的 “栈”,是 JVM 中的一块特定空间。

对于 JVM 虚拟机栈,这里存储的是 方法之间的调用关系。

整个虚拟机栈空间内部,可以认为是包含很多个元素,每个元素是一个 “栈帧”,每一个栈帧,包含方法的入口地址,方法的参数,返回地址,局部变量…

对于本地方法栈,这里存储的是 native 方法之间的调用关系。

认识JVM

由于函数调用,也是有"后进先出"的特点,此处这里的 “栈”,也是"后进先出"的。

数据结构中的"栈",是一个通用的,更广泛的概念,而此处的"栈"特指 JVM 上的一块内存空间。

调用一个方法,就会创建栈帧,方法执行结束了,栈帧就销毁了。

栈空间有上限, JVM 启动的时候是可以设置参数的,其中有一个参数就可以设置占空间的大小。

这里的栈不是只有一个。每个线程有一个,jconsole, 查看 Java 进程内部情况,就可以看到所有的线程,点击线程就可以看到该线程调用栈的情况。

程序计数器

记录当前线程执行到哪个指令了(很小的一块,存一个地址), 每个线程有一份。

整个 JVM 空间中最大的区域。

new 出来的对象,都在堆上,类的成员变量也是在堆上。

堆,是一个进程有一份。

栈,是一个线程有一份。一个进程,有 N 份。

每个 Java 虚拟机(JVM)就是一个进程。

元数据区

Java8 之前叫做方法区。

类对象,常量池,静态成员,都在这里。

一个进程,一块元数据区。

针对内存区域划分:

考点:给你一段代码,问你某个变量在哪个区域上?

局部变量:栈

普通成员变量:堆

静态成员变量:元数据区/方法区

JVM 类加载机制

类加载,就是 .class 从文件(硬盘)被加载到内存(元数据区)中这样的过程。

.java 通过 javac , 得到 .class 文件

认识JVM

加载

加载:把 .class 文件找到,打开文件,读文件,把文件读到内存中。

注意这只是类加载机制的一小步,最终加载完成是要得到类对象。

验证

检查下 .class 文件格式是否正确。(不正确就加载失败)

.class 是一个二进制文件,这里的格式有严格说明,官方提供了 JVM 虚拟机规范,文档上详细描述了 .class 文件的格式。

Java 官方文档:https://docs.oracle.com/javase/specs/index.html

认识JVM

认识JVM

准备

给类对象分配内存空间(在元数据区占个位置),静态变量被初始化为0

解析

初始化字符串常量,把符号引用转为直接引用。

字符串常量,得有一块内存空间,存字符的实际内容。还要有一个引用,来保存这个内存空间的地址。

在类加载之前,字符串常量此时是处在 .class 文件中的,此时这个"引用"记录的并非是 字符串真正的地址,而是它在文件中的"偏移量"(或占位符)。类加载完成之后,字符串常量被加载到内存中,此时才有"内存地址",这个引用才能真正被赋值为"内存地址"。

初始化

针对类对象里的内容进行初始化,执行静态代码块,加载父类…

认识JVM

一个类,啥时候会被加载呢?

不是 Java 程序一运行就把所有的类都加载了,真正要用到才加载,不用就不加载(“懒汉模式”)。

什么情况才算是"用到"?

1.构造类的实例

2.调用类的静态方法/使用静态属性

3.加载子类,就会先加载其父类

一旦加载过了,就不用再重复加载了。

双亲委派模型

加载阶段,要找到 .class 文件,具体去哪里找?双亲委派模型描述的是这个问题。

JVM 默认提供了 3 个类加载器:

1.BootstrapClassLoader : 负责加载标准库中的类( Java 要求提供哪些类) ,不管是哪一种 JVM 的实现都会提供这些一样的类。

2.ExtensionClassLoader : 负责加载 JVM 扩展库中的类(规范之外,由 实现 JVM 的厂商/组织提供的额外的功能)

3.ApplicationClassLoader : 负责加载用户提供的第三方库/用户项目代码中的类

上述三个类加载器,存在"父子关系"

BootstrapClassLoader <- ExtensionClassLoader <- ApplicationClassLoader

每个 ClassLoader 都有一个 parent 属性,指向自己的父 类加载器

上述类加载器如何配合工作?

首先加载一个类,是从 ApplicationClassLoader 开始,

但是 ApplicationClassLoader 会把加载任务交给父亲 ExtensionClassLoader 去执行;

于是 ExtensionClassLoader 要去加载了,但是也不是真加载,而是再委托给自己的父亲;

BootstrapClassLoader 要去加载了,也是想委托给自己的父亲,结果发现自己的父亲是 null,

没有父亲/父亲加载完了,才由自己进行加载。

此时 BootstrapClassLoader 就会搜索自己负责的 标准库目录相关的类,如果找到就加载,没找到就继续由子类加载器加载;

ExtensionClassLoader 真正搜索 扩展库相关的目录,如果找到就加载,没找到就继续由子类加载器加载;

ApplicationClassLoader 真正搜索用户项目相关的目录,如果找到就加载,没找到就继续由子类加载器加载(目前没有子类了,只能抛出"类找不到"这样的异常)。

这个顺序就是为了保证 BootstrapClassLoader 能够先加载,ApplicationClassLoader 后加载。

1.这就可以避免因为用户创建了一些奇怪的类而引起的 bug.

比如,如果用户写了个 java.lang.String 这个类,按照现在这个加载流程,就会先加载标准库的类,而不会加载用户写的这个类。

这样就能保证,即使出现上述问题,也不会让 jvm 已有代码出现混乱,顶多就是用户自己写的类不生效。

另一方面,类加载器是可以用户自定义的,上述 3 个类加载器是 JVM 自带的。

2.用户自定义的类加载器,可以加入到上述流程中,和现有的类加载器配合使用。

类加载,主要是围绕 3 个面试题展开的:

1.类加载的流程

2.类加载的时机

3.双亲委派模型

站在 JVM 的角度,上述 3 个东西都不是类加载的核心,真正的核心应该是 解析.class 文件,解析每个字节是干啥的(验证,准备,解析,初始化)

JVM 垃圾回收机制

垃圾回收机制 GC

啥是垃圾,就是不再使用的内存。

垃圾回收,就是把不用的内存帮我们自动释放了。

GC可以避免内存泄漏问题。

GC 好处:省心,使写代码变得简单,不容易出错。

GC 坏处:需要消耗额外的系统资源,也有额外的性能开销。GC 还有一个 STW(stop the world)问题。

STW:1.如果内存里的垃圾已经很多了,触发一次 GC,开销可能非常大,把系统资源消耗非常多。2.GC可能会涉及到一些锁操作,导致业务代码无法正常执行。这样的卡顿,极端情况下可能是几十ms甚至是上百ms.

GC 主要是针对 堆 进行释放的。

GC ,是以"对象"为单位进行回收的。

认识JVM

GC 实际工作过程

找到垃圾

关键思路,看这个对象有没有"引用"指向它。

如果一个对象有引用指向他,就有可能会被用到;如果没有引用指向它,就不会再被用到了。

  • 具体如何知道,一个对象是否有引用指向它呢?

两种典型实现:

1.引用计数[不是 Java 的做法,python/php]

给每个对象分配了一个计数器(整数)

每次创建一个引用指向该对象,计数器 + 1;每次引用销毁,计数器 - 1

认识JVM

这个方法简单有效,但 Java 没有使用,原因 :1.内存空间浪费的多,每个对象都要分配一个计数器,如果代码里的对象非常多,占用的额外空间就会很多,尤其是每个对象非常小的情况下。2.存在循环引用的问题。

认识JVM

Python/PHP 使用引用计数,需要搭配其他的机制,来避免循环引用的问题。

2.可达性分析[ Java 的做法]

Java 里的对象,都是通过引用来指向并访问的,经常有一个引用指向一个对象,这个对象的成员又指向另一个对象。

class TreeNode{
    int value;
    TreeNode left;
    TreeNode right;
}
TreeNode root = new TreeNode();
root.left = 

整个 Java 里所有的对象,就通过链式/树形结构,整体给串起来。

这些对象被组织的结构视为树。可达性分析,就是从树根节点出发,遍历树,所有能被访问到的对象,标记为"可达",不能访问到的,就是"不可达",GC把"不可达"的作为垃圾回收了。

可达性分析,需要进行类似于"树遍历"这样的操作,相比于引用计数来说,肯定是要慢一些的。但是速度慢,没关系,可达性分析遍历操作,并不需要一直执行,隔一段时间分析一遍就可以了。

进行可达性分析,遍历的起点,称为 GCroots.

GCroots,有以下几种:

1.栈上的局部变量

2.常量池中的对象

3.静态成员变量

一个代码中有很多个这样的起点,把每个起点都往下遍历一遍,就完成了一次扫描过程。

清理垃圾

主要是 3 种基本做法:

1.标记清除

认识JVM

2.复制算法

解决了内存碎片问题。

认识JVM

每次触发算法, 都是向另外一侧进行复制。

缺点:1.空间利用率低 2.如果垃圾少,有效对象多,复制成本就比较大。

3.标记整理

解决复制算法的缺点。

标记整理,就是类似于顺序表删除中间元素,会有元素搬运的操作。保证了空间利用率,也解决了内存碎片的问题。

这种做法效率也不高,如果要搬运的空间比较大,开销也会比较大。

上述做法并不完美。基于上述这些基本策略,搞了一个复合策略"分代回收"。

把垃圾回收分成不同的场景,有的场景用这个算法,有的场景用那个算法,扬长避短。

Java 中的对象,要么就是生命周期特别长,要么就是特别短。根据生命周期的长短,分别使用不同的算法。给对象引入一个概念,年龄(熬过 GC 的轮次)。年龄越大,这个对象存在的时间就越久。

经过这一轮可达性分析的遍历,发现这个对象还不是垃圾,这就是"熬过一轮 GC"。

认识JVM

新生代:刚 new 出来的,年龄是 0 的对象,放到伊甸区。熬过一轮 GC ,就要放到幸存区。

Java 对象一般都是朝生夕死,生命周期非常短。所以幸存区够放。

伊甸区 -> 幸存区 通过"复制算法"。

到幸存区之后,也要周期性的接受 GC 的考验。如果变成垃圾,就被释放掉;如果不是垃圾就被放到另一个幸存区。这 2 个幸存区同一时刻只用一个。在这两者之间来回拷贝(复制算法)。

如果这个对象在幸存区已经来回拷贝很多次了,这个时候就要进入老年代了。

老年代都是年龄大的对象,生命周期更长,针对老年代,也要周期性的进行 GC 扫描,但是频率更低了。

如果老年代的对象是垃圾了,就使用标记整理的方式进行释放。

上述介绍的是 GC 典型的垃圾回收算法。

如何确定垃圾+如何清理垃圾 这些介绍的都是策略,

实际 JVM 实现的时候会有一定差异,JVM 有很多 垃圾回收实现,称为"垃圾回收器"。

垃圾回收器的具体实现,会围绕上述算法思想展开,会有一些变化。

不同的 垃圾回收器的侧重点不同,有的追求扫的快,有的追求扫的好,有的追求对用户的打扰少(STW 尽量短)

垃圾回收器举例:CMS, G1, ZGC文章来源地址https://www.toymoban.com/news/detail-413499.html

到了这里,关于认识JVM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一、认识 JVM 规范(JVM 概述、字节码指令集、Class文件解析、ASM)

    JVM : Java Virtual Machine ,也就是 Java 虚拟机 所谓虚拟机是指:通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的计算机系统。 即:虚拟机是一个计算机系统。这种计算机系统运行在完全隔离的环境中,且它的硬件系统功能是通过软件模拟出来的。 JVM 通

    2024年01月23日
    浏览(39)
  • 形象谈JVM-第二章-认识编译器

    我在上一章《形象谈JVM-第一章-认识JVM》提到的“翻译”,其实就是我们今天所说的“编译”的概念。 上一章原文链接:https://www.cnblogs.com/xingxiangtan/p/17617654.html 原文: 【 虚拟机的职责是将字节码翻译成对应系统能够识别并执行的机器码, 比如在linux系统,java文件被javac编译

    2024年02月13日
    浏览(32)
  • 了解 JVM - 认识垃圾回收机制与类加载过程

    本篇通过介绍JVM是什么,认识JVM的内存区域的划分,了解类加载过程,JVM中垃圾回收机制,从中了解到垃圾回收机制中如何找到存活对象的方式,引用计数与可达性分析的方式,再释放垃圾对象时使用的方式,标准清除,复制算法,标准整理,分代回收等等,如有错误,请在

    2024年02月16日
    浏览(31)
  • 从官网认识 JDK,JRE,JVM 三者的关系

    点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~ JVM 是一些大厂面试必问点,要想解决 OOM、性能调优方面的问题,掌握 JVM 知识必不可少,从今天开始,将为大家介绍 JVM 的常用知识。 1、Java 官网 Java 官网主页:https://docs.oracle.com/en/java/in

    2024年02月15日
    浏览(38)
  • BDA初级分析——认识SQL,认识基础语法

    SQL作为实用技能,热度高、应用广泛  在对数据分析人员的调查中SQL长期作为热度排名第-一的编程语言超过Python和R SQL:易学易用,高效强大的语言 SQL: Structured Query Language 结构化查询语言 SQL:易学易用,类似英文语法类的语言结构  SQL:标准语句,写法成熟,应用广泛 SQ

    2024年02月12日
    浏览(21)
  • 【微服务】微服务初步认识 - 微服务技术如何学习 · 认识微服务架构

    微服务(1) 微服务只是分布式架构中的一种,而SpringCloud只是解决了服务拆分时的服务治理问题,至于其他的分布式的更复杂的问题并没有明确的给出解决方案,所以微服务不仅仅包含SpringCloud,还包含一些其他的~ 对于SpringCloud相关的微服务(架构)治理工作(大概了解,先不

    2024年02月08日
    浏览(35)
  • 【Linux】认识Linux下的编译器gcc/g++ | 认识动静态库

    本文思维导图: 🍉博主主页:@在肯德基吃麻辣烫 我们知道,sudo指令后面紧跟的一条指令,该条指令是以root身份执行的对于有一些我们普通用户无法执行的指令,就需要用到该指令进行提权。 sudo + 指令 功能:以管理员身份执行该条指令 比如: sudo touch mycode.c 然而我们执行

    2024年02月08日
    浏览(33)
  • 英飞凌TC3xx之一起认识GTM系列(一)先来认识GTM架构

    GTM系统使用GTM全局时钟f GTM 运行(本文称为SYS_CLK)。 特点如下 : GTM模块由两个主要部分组成: 由博世设计的GTM IP v3.1.5.1 GTM IP 由许多不同的子模块组成,提供多种功能来解决所有问题 与定时器模块相关的最常见应用 由英飞凌设计的GTM Wrapper 以下是子模块中一些最重要的

    2024年02月03日
    浏览(35)
  • 认识Filter(过滤器)

    Filter介绍 在计算机编程中,Filter(过滤器)是一种用于对数据流进行处理的软件组件。Filter 的作用是从输入流中获取数据,对其进行处理后再将其写入输出流中。Filter 组件通常用于数据校验、数据转换、数据压缩等方面,以及对网络通信进行处理。在 Web 开发中,Filter 是

    2024年02月02日
    浏览(39)
  • 认识容器,走进Docker

    容器技术简介 一切在云端,万物皆容器,说到容器,大家都会想到Docker,Docker现在几乎是容器的代名词,什么是Docker? 为什么会出现呢? 正如所谓的AB法则,before之前是什么,为什么出现after,互联网公司技术部门开发工程师、测试工程师、运维工程师、常常因为的所使用的

    2024年02月12日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包