【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,一经查实,立即删除!

相关文章

springboot中logback日志配置

springboot中logback日志配置 前言默认配置logback-spring.xml详细配置 前言 Spring Boot使用Apache的Commons Logging作为内部的日志框架,其仅仅是一个日志接口,在实际应用中需要为该接口来指定相应的日志实现。 Spring Boot从1.4版本开始内置的日志框…

程序员面试系列,kafka常见面试题

原文链接 Kafka是什么?它的主要作用是什么?什么是Kafka的主题(Topic)和分区(Partition)?Kafka中的消息是如何被生产者发送和消费者接收的?Kafka中的分区有什么作用?为什…

聊聊Linq中.AsEnumerable(), AsQueryable() ,.ToList(),的区别和用法

聊聊Linq中.AsEnumerable(), AsQueryable() ,.ToList(),的区别和用法 当使用LINQ查询数据时,我们常常会面临选择使用.AsEnumerable(), .AsQueryable(), 和 .ToList()方法的情况。这些方法在使用时有不同的效果和影响,需要根据具体场景来选择合适的方法。…

springboot热加载spring-boot-devtools:

springboot热加载 基于idea开发springboot项目使用热加载 pom依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</op…

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

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

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

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

Go 语言 值类型和引用类型

Go 语言 值类型和引用类型 值类型&#xff1a; 概述&#xff1a; 值类型的人变量直接存储其值&#xff0c;他们通常在栈上分配内存。当把一个值类型的变量赋值给另外一个变量、作为函数参数传递或从函数返回时&#xff0c;进行值的复制。因此每个变量都有自己独立的存储&…

Flutter 自定义 虚线 分割线

学习使用Flutter 进行 虚线 自定义控件 练习 // 自定义虚线 &#xff08;默认是垂直方向&#xff09; class DashedLind extends StatelessWidget {final Axis axis; // 虚线方向final double dashedWidth; // 根据虚线的方向确定自己虚线的宽度final double dashedHeight; //…

实训笔记7.24

实训笔记7.24 7.24笔记一、Hadoop中MapReduce框架的使用原理和流程1.1 涉及到一些框架核心组件1.1.1 InputFotmat1.1.2 MapTask1.1. 3Partitioner1.1.4 WritableComparable1.1.5 Combiner&#xff08;可选&#xff09;1.1.6 WritableComparator(GroupingComparator)1.1.7 Reduc…

c++ 之 dynamic_pointer_cast

/* * dynamic_pointer_cast */ 描述 (Description) 它返回一个正确类型的sp副本&#xff0c;其存储的指针从U *动态地转换为T *。 声明 (Declaration) 以下是std :: dynamic_pointer_cast的声明。 template <class T, class U> shar…

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

一、线程隔离 &#x1f349; 线程隔离有两种方式实现&#xff1a; 线程池隔离信号量隔离&#xff08;Sentinel默认采用&#xff09; 线程隔离&#xff08;舱壁模式&#xff09;&#x1f95d; 在添加限流规则时&#xff0c;可以选择两种阈值类型&#xff1a; QPS&#xff1a;…

SpringBoot-4

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

NLP 中的pad/padding操作代码分析

今天分析一下NLP中的pad操作代码&#xff1a; 该方法的作用是将输入的序列列表seqs进行填充操作&#xff0c;使其具有相同的长度&#xff0c;以便进行批处理。填充使用指定的pad_token进行&#xff0c;并生成一个对应的mask标志列表&#xff0c;用于标记哪些部分是填充内容&am…

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

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

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

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

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

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

数学学习总结

最近在准备一场考试&#xff0c;通过这几个月的学习发现数学思维还是有待建立&#xff0c;逻辑性、熟练度、思维想象力还是不足&#xff0c;本身数学基础不扎实&#xff0c;要通过这场考试&#xff0c;需要更进一步努力&#xff0c;复习一轮后&#xff0c;看视频、看老师讲解都…

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

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

Docker介绍以及实战教程

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

【java安全】RMI

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