JVM——类加载和垃圾回收

目录

前言

JVM简介

JVM内存区域划分

JVM的类加载机制

1.加载

双亲委派模型

2.验证

验证选项

3.准备

4.解析

5.初始化

触发类加载

JVM的垃圾回收策略 GC

一:找     谁是垃圾 

1.引用计数

2.可达性分析  (这个方案是Java采取的方案)。

二:释放垃圾对象

三种典型的策略

JVM实现思路


前言

我们在学习JVM的时候,其实里面的内容是非常之多的,但是里面的大部分内容都是属于八股,想要彻底搞明白,就需要看大量的关于JVM的源代码,JVM的源代码是C++写的。想要深入研究的可以去看看《深入理解Java虚拟机》这本书。

这篇文章主要针对JVM中的常见的面试题来展开。

JVM简介

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。
虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。
常见的虚拟机:JVM、VMwave、Virtual Box。
JVM 和其他两个虚拟机的区别:

  1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;
  2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪。

JVM 是一台被定制过的现实当中不存在的计算机。

JVM内存区域划分

JVM其实就是一个Java进程,Java进程也就是JVM会从操作系统这里申请一大块内存空间,给Java代码来使用。

JVM从操作系统申请的这块内存空间中,进行进一步的划分,给出了每块划分后的空间的不同用途。

其中,最核心的就是栈、堆、元数据区(方法区)。

  • 虚拟机栈是给Java代码来使用的,主要存放一些局部变量,还有维护方法之间的调用关系。
  • 本地方法栈则是给JVM内部的本地方法来使用的。
  • 堆上存放的就是new出来的对象、成员变量。
  • 程序计数器中存放的就是一个内存地址,这个内存地址就是下一个要执行字节码所在的地址,作用就是记录当前程序执行到那个指令了。

需要注意的是,堆和元数据区,在一个JVM 中只存在一份,也就是多个线程共享堆区和元数据区。

栈(本地方法栈和虚拟机栈)和程序计数器则是存在多份的,也就是每个线程都会有一份。

JVM的线程操作和操作系统的线程操作是一对一的关系。也就是说每次在Java代码中创建的线程都会在操作系统中有一个线程与之对应。

这里的面试题主要就是判断某个变量或者对象在JVM的那个区域?

例如下面代码:

void func() {Test t1 = new Test();
}

上述代码在一个方法里面我们实例化了一个Test对象。

 func方法是在元数据区以一些二进制的指令来存储的。

我们可以看到t1变量是一个在方法里面定义的,所以他是一个局部变量,局部变量就存储在栈上。

而new Test(); 这个对象的本体则是在堆上的。

其实像这里的关于JVM区域的面试题,我们只需要知道JVM的每个区域都是存储什么东西的就好了。

  • 虚拟机栈是给Java代码来使用的,主要存放一些局部变量,还有维护方法之间的调用关系。
  • 本地方法栈则是给JVM内部的本地方法来使用的。
  • 堆上存放的就是new出来的对象、成员变量。
  • 程序计数器中存放的就是一个内存地址,这个内存地址就是下一个要执行字节码所在的地址,作用就是记录当前程序执行到那个指令了。

JVM的类加载机制

对与一个类来说,他的生命周期是这样的:

 前面的5步也是类加载的过程和固定的顺序。我们主要研究前面的5步。

类加载具体就是把一个.class文件,也就是类编译后的文件,加载到内存中,得到了类对象这样的过程就称之为类加载。

一个程序想要运行,就需要把指令和数据加载到内存中。类加载就是做的这个事情。

下面是类加载的5个步骤:

1.加载

这里的加载过程其实简单,就是找到.class文件,然后读取文件的内容。

但是在找.class文件的这个过程中,会有一个非常重要的机制:双亲委派模型

双亲委派模型

在JVM中,加载类需要用到一组特殊的模块:类加载器。

在JVM中,内置了三个类加载器。

  • BootStrap ClassLoader    负责加载Java标准库中的类
  • Extension ClassLoader     负责加载一些非标准的但是是Sun/Oracle扩展库的类
  • Application ClassLoader    负责加载项目中自己写的类、以及第三方库中的类

当具体加载一个类的时候,他的过程是这样的:

需要先给定一个类的全限定类名,"java.lang.String"  这个类名是一个字符串的形式。

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层BootStrap ClassLoader类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

具体可以参考下图:

2.验证

由于.class文件有着明确的数据格式(二进制的),这一阶段的主要目的就是确保Class文件中的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求。

验证选项

文件格式验证

字节码验证

符号引用验证……

3.准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。

比如下面这样的代码:

public static int value = 123;

此时在准备阶段value的值并不是123,而是0。
 

4.解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

  • 符号引用:就是字符串常量在.class文件已经存在,但是他们只知道彼此之间的相对位置,并不知道自己在内存中的具体位置。
  • 直接引用:真正的加载到内存中,就会把字符串常量填充到内存中的特定地址上去。此时字符串引用的就是直接引用,(也就是Java中普通的引用)。

5.初始化

在初始化阶段,JVM才真正的执行类中编写的Java代码,将主导权交给应用程序,初始化阶段就是执行类的构造方法的过程。(类要是有父类,就需要先初始化父类,在初始化子类)。

触发类加载

注意:类加载这个动作不是说JVM一启动就会进行加载,因为JVM整体是一个懒加载的策略,也就是非必要,不加载。

以下三种请况就会加载:

  1. 创建了这个类的实例
  2. 使用了这个类的静态方法/静态属性
  3. 使用子类,会触发父类的加载

JVM的垃圾回收策略 GC

Java中的垃圾回收是为了帮助我们自动释放内存的一种机制。

面试题:为什么需要垃圾回收机制

因为在程序运行过程中,会向操作系统申请大量的内存空间,但是这些空间也有可能会消耗尽,因为不断地分配内存空间而不进行回收,就好像不停地生产生活垃圾而从来不打扫一样。

上面我们谈到了关于JVM的几个区域,那么垃圾回收释放的是那个区域的空间呢?

需要注意的是,栈和程序计数器是每个线程都会有一份的。他们会随着线程的销毁而一起销毁的。

而元数据区里面的存储的类对象,很少会进行销毁。

所以我们释放的就是堆中的空间。上面我们谈到堆中主要就是存放new 出来的对象的。

GC也就是以对象为单位进行释放的。(释放对象)

GC中主要分为两个阶段:

一:找     谁是垃圾 

Java通过引用来判断是否是垃圾对象,如果没有引用指向,就判定这个对象是垃圾。

1.引用计数

给对象安排一个额外的空间,保存了一个整数,表示该对象有几个引用指向它。Java实际上并没有采取这样的方案,(Python、PHP采用了这个方案)。

Test t1 = new Test();

 此时是有一个引用指向的,所以引用计数器为1。

如果代码变成这样:

Test t1 = new Test();
Test t2 = t1;

 也就是说随着引用的增加,计数器就会增加,引用的销毁,计数器就会减少。

当计数器为0时,就会认为该对象没有引用指向了,就是垃圾了。

但是缺点也是很明显:

  1. 浪费内存空间
  2. 存在循坏引用的情况

2.可达性分析  (这个方案是Java采取的方案)。

把对象之间的引用关系理解成为了一个树形结构,从一些特殊的起点出发,进行遍历,只要能访问到,是可达的,不是垃圾,再把不可达的当做垃圾即可。

 此时通过root这个引用是可以访问到整个树的任意节点的。

可达性分析的关键要点在于要进行上述的遍历,需要有起点的。

起点可以是:

  1. 栈上的局部变量(每个栈的每个局部变量都是起点)
  2. 常量池中引用的对象
  3. 方法区中静态成员引用的对象

可达性分析,总体就是从所有的起点出发,看看该对象里面又通过哪些引用能访问到那些对象,顺藤摸瓜的把所有可以访问的对象都访问一遍,遍历的同时把对象标记为“可达”。

可达性分析,克服了引用计数的两个缺点

但是也是有自己的问题:

  • 消耗更多的时间 因此即使某个对象成了垃圾,也不能第一时间发现,因为在扫描的过程中,也是需要时间的。
  • 在进行可达性分析的时候,要顺藤摸瓜,一旦这个过程中,当前代码中的对象的引用关系发生了变化,就可以出现bug。

因此为了更好的完成这个顺藤摸瓜的过程,就需要让其他的业务线程都暂停工作!!!(STW)

(STW)   stop the world !

但是Java毕竟发展了这么多年,拉进回收这里也是在不断的进行优化,STW这个问题也可以比较好的对付了。

二:释放垃圾对象

三种典型的策略

1:标记清除

 如果现在向内存申请了一块下面这样的空间,然后我标出来的就是垃圾对象,需要清除的。

 这种策略就是直接把垃圾对象的内存就释放了。

但是这种简单粗暴的方式会产生内存碎片。

内存碎片:申请空间都是连续的整块空间,现在上述图中的空闲空间都是散落在独立的空间里面的。现在空闲总空间可能超过1G,但是我想申请500M,却是申请不了。

2:复制算法

这种方法是把空间分为两部分。一次只使用一半。

复制算法就是把不是垃圾的对象拷贝到一边去,然后在统一释放整个区域。

 此时我要释放的是2和4,我就需要把剩下1和3复制到另一边去。然后再把这边全部释放。

 复制算法解决了内存碎片的问题,但是也有缺点:

  • 内存利用率比较低
  • 如果大部分对象都是保留的,垃圾很少,此时的复制成本就比较高

3:标记整理

类似于顺序表删除中间元素,有一个搬运的过程

 解决了内存碎片问题但是搬运的整体开销也是比较大的。

JVM实现思路

实际上,JVM的实现方式是结合了上述几种思想之后的方法。

分代回收思想

具体细节:

  • 给对象设置年龄这样的概念,用来描述这个对象存在多久了。如果一个对象刚诞生,那么就是0岁。
  • 每次进过一次扫描(可达性分析)如果没有被标记为垃圾对象,这是对象年龄就增加一岁。
  • 通过年龄来区分这个对象的活动时间。

经验规律:年龄越大的对象,也将会持续存在更长的时间。

针对不同的年龄来采取不同的回收策略

JVM针对这几个区域来执行不同的策略。

1:新创建的对象,放在伊甸区

垃圾回收扫描到伊甸区之后,大多数的对象将会在第一轮扫描下被GC给淘汰掉。

2:如果伊甸区的对象,熬过第一轮GC,就会通过复制算法,拷贝到生存区。

生存区分为两半(大小相等),一次只使用其中的一半。

如果GC在扫描生存区的时候,发现垃圾对象也就淘汰,不是垃圾的,就通过复制算法拷贝到生存区的另一边。

3:当对象在生存区熬过了若干次GC的时候,年龄也变大了。此时就会通过复制算法拷贝到老年代。

4:进入老年代之后,由于年龄都比较大了,被标记为垃圾对象的概念也很小,所以针对老年代的GC扫描也会降低频率。

特殊情况:如果对象非常大,直接进入老年代(大对象进行复制算法,成本非常高,而且大对象也不会很多)。

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

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

相关文章

基于单片机智能台灯坐姿矫正器视力保护器的设计与实现

功能介绍 以51单片机作为主控系统;LCD1602液晶显示当前当前光线强度、台灯灯光强度、当前时间、坐姿距离等;按键设置当前时间,闹钟、提醒时间、坐姿最小距离;通过超声波检测坐姿,当坐姿不正容易对眼睛和身体腰部等造成…

MySQL索引详解

索引 在MySQL中,查询方式可以根据访问表数据的方式分为两种:全表扫描和使用索引。 全表扫描(Full Table Scan): 全表扫描是指在查询过程中,MySQL会遍历整个表的每一行来检查满足查询条件的数据。当查询条件…

Ubuntu下安装、配置及重装CUDA教程

安装CUDA 前往Nvidia CUDA Tools官网选择对应的架构和版本下载CUDA 以如下架构和版本为例: 查看显卡驱动 nvidia-smi如果显卡驱动已经装了,那么在CUDA安装过程中不用再勾选安装driver 下载并安装CUDA wget https://developer.download.nvidia.co…

Spring AOP

目录 AOP 理解AOP AOP组成 AOP的优点 Spring AOP 使用Spring AOP 定义切面和切点 定义通知 动态代理 织入 AOP 理解AOP AOP即面向切面编程,简单来说,就是把一部分通用的功能集中的放在一个地方处理的思想。假如某一段代码很多地方要用到&…

创建型模式 - 建造者模式

概述 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。 由于实现了构建和装配的解耦。…

pytest 参数化进阶

目录 前言: 语法 参数化误区 实践 简要回顾 前言: pytest是一个功能强大的Python测试框架,它提供了参数化功能,可以帮助简化测试用例的编写和管理。 语法 本文就赶紧聊一聊 pytest 的参数化是怎么玩的。 pytest.mark.par…

openGauss学习笔记-13 openGauss 简单数据管理-DELETE语句

文章目录 openGauss学习笔记-13 openGauss 简单数据管理-DELETE语句13.1 语法格式13.2 参数说明13.3 示例 openGauss学习笔记-13 openGauss 简单数据管理-DELETE语句 DELETE语句可以从指定的表里删除满足WHERE子句的行。如果WHERE子句不存在,将删除表中所有行&…

css 禁止多次点击导致的选中了目标div的文字

像下面这样的情况,就可以用这种方法避免掉 禁止多次点击,导致的,选中了目标div的文字 或者 禁止多次点击,导致,html结构被选中显示出来 .targetDiv {-webkit-user-select: none;-moz-user-select: none;-ms-user-sel…

【云原生】Docker的初步认识,安装与基本操作

一、Docker的相关知识 Docker是一个开源的应用容器引擎,基于go语言开发并遵循了apache2.0协议开源。 Docker是在Linux容器里运行应用的开源工具,是一种轻量级的“虚拟机”。 Docker 的容器技术可以在一台主机上轻松为任何应用创建一个轻量级的、可移植的…

SpringCloud整合Sentinel

文章目录 1、Sentinel介绍2、安装Sentinel控制台3、微服务整合Sentinel 1、Sentinel介绍 阿里开源的流量控制组件官网:https://sentinelguard.io/zh-cn/index.html承接了阿里双十一大促流量的核心场景,如秒杀、消息削峰填谷、集群流量控制、实时熔断下游…

Python自动化之pytest常用插件

目录 1、失败重跑 pytest-rerunfailures 2、多重校验 pytest-assume 3、设定执行顺序 pytest-ordering 4、用例依赖(pytest-dependency) 5.分布式测试(pytest-xdist) 6.生成报告(pytest-html) 1、失败重跑 pytest-rerunfailu…

Flutter 小技巧之滑动控件即将“抛弃” shrinkWrap 属性

相信对于 Flutter 开发的大家来说, ListView 的 shrinkWrap 配置都不会陌生,如下图所示,每当遇到类似的 unbounded error 的时候,总会有第一反应就是给 ListView 加上 shrinkWrap: true 就可以解决问题,那为什么现在会…

椒图——靶场模拟

先查看ip,10.12.13.232模拟的外网ip,其他的模拟内网ip,服务里面搭建好的漏洞环境。 #第一个测试项目,web风险发现 新建,下发任务,点威胁检测,webshell,点扫描任务,点新…

QT中QTimer的循环时间与槽函数执行时间以及在事件循环中触发,不同时间的结果分析

目录 当循环时间小于槽函数时间时: 当循环间隔时间大于槽函数时间时: 当存在两个定时器器,其中一个还是间隔100ms,另一个间隔1000ms: 当两个定时器的循环周期大于槽函数执行时间时 当在主程序中添加一个for循环…

js - 对forEach()函数的一些理解

1,定义和用法 定义: forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。注意: forEach() 对于空数组是不会执行回调函数的。 用法: // 箭头函数 forEach((element) > { /* … */ }) forEach((element, index) &…

mcu 启动流程

MCU启动流程 MCU启动流程 MCU启动流程1 MCU的启动方式2 MCU程序启动执行过程3 启动过程的执行工作4 keil调式过程验证5 调试文件map 1 MCU的启动方式 单片机的启动方式,以stm32为例,如下: 不同的下载方式对应的不同的启动方式,st…

truffle 进行智能合约测试

本方法使用了可视化软件Ganache 前两步与不使用可视化工具的步骤是一样的(有道云笔记),到第三步的时候需要注意: 在truffle插件下找到networks目录,提前打开Ganache软件 在Ganache中选择连接或者新建,我在…

如何学习Java集合框架? - 易智编译EaseEditing

要学习Java集合框架相关的技术和知识,可以按照以下步骤进行: 掌握Java基础知识: 在学习集合框架之前,确保你已经具备良好的Java编程基础,包括语法、面向对象编程(OOP)原理和常用的核心类库等。…

MySQL备份与还原/索引/视图

MySQL备份与还原/索引/视图练习 文章目录 一、备份与还原1、使用mysqldump命令备份数据库中的所有表2、备份booksDB数据库中的books表3、使用mysqldump备份booksDB和test数据库4、使用mysqldump备份服务器中的所有数据库5、使用mysql命令还原第二题导出的book表6、进入数据库使…

STM32案例学习 GY-39环境监测传感器模块

STM32案例学习 GY-39环境监测传感器模块 硬件平台 野火STM32F1系列开发板正点STM32F1系列开发板STM32F103ZET6核心板GY-39环境监测传感器模块 GY-39环境监测传感器模块 GY-39 是一款低成本,气压,温湿度,光强度传感器模块。工作电压 3-5v…