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

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.mzph.cn/news/7115.shtml

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

相关文章

金融领域:产业链知识图谱包括上市公司、行业和产品共3类实体,构建并形成了一个节点10w+,关系边16w的十万级别产业链图谱

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实…

前端 | ( 十三)CSS3简介及基本语法(下)| 伸缩盒模型 | 尚硅谷前端html+css零基础教程2023最新

学习来源:尚硅谷前端htmlcss零基础教程,2023最新前端开发html5css3视频 系列笔记: 【HTML4】(一)前端简介【HTML4】(二)各种各样的常用标签【HTML4】(三)表单及HTML4收尾…

微服务保护——Sentinel【实战篇二】

一、线程隔离 🍉 线程隔离有两种方式实现: 线程池隔离信号量隔离(Sentinel默认采用) 线程隔离(舱壁模式)🥝 在添加限流规则时,可以选择两种阈值类型: QPS:…

SpringBoot-4

Spring Boot 使用 slf4j 日志 在开发中经常使用 System.out.println()来打印一些信息,但是这样不好,因为大量的使用 System.out 会增加资源的消耗。实际项目中使用的是 slf4j 的 logback 来输出日志,效率挺高的,Spring Boot 提供…

如何用3D格式转换工具HOOPS Exchange读取颜色和材料信息?

作为应用程序开发人员,非常希望导入部件的图形表示与它们在创作软件中的外观尽可能接近。外观可以在每个B-Rep面的基础上指定,而且,通过装配层次结构的特定路径可以在视觉外观上赋予父/子覆盖。HOOPS ExchangeHOOPS Exchange可捕获有关来自各…

新零售数字化商业模式如何建立?新零售数字化营销怎么做?

随着零售行业增速放缓、用户消费结构升级,企业需要需求新的价值增长点进行转型升级,从而为消费者提供更为多元化的消费需求、提升自己的消费体验。在大数据、物联网、5G及区块链等技术兴起的背景下,数字化新零售系统应运而生。 开利网络认为&…

让GPT人工智能变身常用工具-上

1.密码生成器:GPT为您创建安全密码 想象GPT作为您的个人密码生成器,负责从头到尾为您创建复杂且安全的密码。您只需要告诉他您的密码需求,比如密码的长度,是否包含大写字母、小写字母、数字或特殊字符,他会立即为您生成一个复杂但经过深度设计的密码。 例子: 我希望您…

Python 单继承、多继承、@property、异常、文件操作、线程与进程、进程间通信、TCP框架 7.24

单继承 class luban:def __init__(self, name):self.name nameself.skill "摸鱼飞弹"self.damageLevel 20def attack(self):print("{} 使用了技能{} ,给敌方带来了极大的困扰\n""并有{}% 的机会造成一击必杀的效果".format(self.…

Docker介绍以及实战教程

Docker简介 Docker为什么出现 从事软件开发的朋友,可能经常会碰到以下场景:运维:你这程序有Bug啊,怎么跑不起来啊!开发:我机子上能跑啊,你会不会用啊究其原因还是开发环境与生产环境不同造成的…

【java安全】RMI

文章目录 【java安全】RMI前言RMI的组成RMI实现Server0x01 编写一个远程接口0x02 实现该远程接口0x03 Registry注册远程对象 Client 小疑问RMI攻击 【java安全】RMI 前言 RMI全称为:Remote Method Invocation 远程方法调用,是java独立的一种机制。 RM…

SoapUI、Jmeter、Postman三种接口测试工具的比较分析

前段时间忙于接口测试,也看了几款接口测试工具,简单从几个角度做了个比较,拿出来与诸位分享一下。本文从多个方面对接口测试的三款常用工具进行比较分析,以便于在特定的情况下选择最合适的工具,或者使用自己编写的工具…

12.(开发工具篇vscode+git)vscode 不能识别npm命令

1:vscode 不能识别npm命令 问题描述: 解决方式: (1)右击VSCode图标,选择以管理员身份运行; (2)在终端中执行get-ExecutionPolicy,显示Restricted&#xff…

【主成分分析(PCA)】

主成分分析(PCA) 摘要 在现代数据科学中,维度灾难常常是数据处理与分析的一大难题。主成分分析(PCA)是一种广泛使用的数据降维技术,它通过将原始数据转换为新的低维空间,保留最重要的信息&…

C国演义 [第十一章]

第十一章 有效的字母异位词题目理解代码 两数之和题目理解(暴力篇)代码题目理解(哈希篇)代码 有效的字母异位词 力扣链接 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词 注意:若 s 和 t 中每个字符出现的次数都相同,…

git常用命令

git安装后-指定名称和邮箱 $ git config --global user.name “Your Name” $ git config --global user.email “emailexample.com” 本地初始化GIT 仓库: #基于远程仓库克隆至本地 git clone <remote_url> #当前目录初始化为git 本地仓库 git init “directory” 把文…

Linux:多进程和多线程回环socket服务器和客户端

多进程socket服务器代码&#xff1a; #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <string.h> #include <ctype.h> #include <sys/wait.h> #i…

Facebook Messenger市场营销,跨境电商不可忽略的营销手段

营销始于广告。广告仍然是不可或缺的&#xff0c;但广告的方式正在发生变化。以前商家会使用广告邮件或者直接转到网站上的产品页面&#xff0c;但是这两种方法都存在很大问题。虽然企业可以通过电子邮件与潜在客户保持联系&#xff0c;但不能保证这些潜在客户会真正看广告邮件…

Gitee 上传项目到仓库(上传文件夹)

一、将仓库下载到本地 1.首先打开仓库&#xff0c;点击下载压缩包 2.将下载的压缩包解压&#xff0c;并打开&#xff0c;在当前目录下打开 二、git操作 1.在文件当前目录打开git bash 2.初始化git git init 该命令会生成一个隐藏的.git文件夹 如果不是第一次使用&#…

精通正则表达式 - 打造高效正则表达式

目录 一、典型示例 1. 稍加修改——先迈最好使的腿 2. 效率 vs 准确性 3. 继续前进——限制匹配优先的作用范围 4. “指数级”匹配 二、全面考察回溯 1. 传统 NFA 的匹配过程 2. POSIX NFA 需要更多处理 3. 无法匹配时必须进行的工作 4. 看清楚一点 5. 多选结构的代…

测试用例实战

测试用例实战 三角形判断 三角形测试用例设计 测试用例编写 先做正向数据&#xff0c;再做反向数据。 只要有一条边长为0&#xff0c;那就是不符合要求&#xff0c;不需要再进行判断&#xff0c;重复。 四边形 四边形测试用例