【JVM】深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制

news2024/12/27 11:42:34

我的主页:2的n次方_     

在这里插入图片描述

1. JVM 内存区域划分

  1. 程序计数器(空间比较小)。保存了下一条要执行的指令的地址(指向元数据区指令的地址)
  2. 堆。JVM 最大的空间,new 出来的对象都在堆上
  3. 栈。函数中的局部变量,函数的形参,函数之间的调用关系都存放在栈上。栈也可以分为 Java 虚拟机栈和本地方法栈,Java 虚拟机栈主要服务于 Java 方法的执行,而本地方法栈主要服务于本地方法(C/C++实现的方法)
  4. 元数据区(方法区)。存放 Java 程序中的指令(指令都是包含在类的方法中),也就是保存了代码中涉及到类的相关信息,同时也保存了类的 static 属性

在一个 Java 进程中,元数据区和堆是只有一份的,程序计数器和栈可能有多份(当一个 Java 进程中有多个线程的时候,每个线程都有自己的程序计数器和栈,因为每个需要记录自己接下来需要执行什么指令还有其中的函数调用关系),由于堆和元数据区是共用一份的,所以如果是在堆上 new 了一个对象,或是元数据区的一个 static 属性,每一个线程都可以访问。

来看下面创建的变量都处在哪个内存区域:

一个变量处在哪个内存区域和变量是不是“内置类型”无关,而是和变量的形态有关

局部变量:栈 成员变量:堆 静态成员变量:元数据区(方法区)

所以说 a,b在堆区 c,d 在方法区,e,f 在元数据区(方法区)

2. JVM 类加载的过程

我们平时写的 Java 代码是一个 .java 文件,通过 javac 编译成 .class 文件,既然是文件,那么都是存储在硬盘上的,当运行 Java 进程的时候,JVM 就需要读取 .class 中的内容,并且执行里面的指令,这就是类加载的过程,也就是把类涉及到的字节码从硬盘上读取到内存中(元数据区), .class 中的指令转变为类对象,类对象中就包含了 .class 文件的各种信息,比如:类的名字,类中定义的属性,方法,实现的接口和继承关系等。

下面介绍一下类加载的具体过程:

2.1. 加载

  1. 加载。找到对应的 .class 文件,通过类的全限定名打开并读取文件内容,获取定义此类的二进制字节流。

2.2. 验证

  1. 验证。验证是确保 Class 文件的字节流中包含的信息符合当前 JVM 的要求,在 Java 标准文档中,也明确定义了 Class 文件的格式

2.3. 准备

  1. 准备。准备阶段是正式为类中被 static 修饰的变量(类变量)在方法区中分配内存空间的,这些类变量在整个类的生命周期中都是有效的,并且被所有的实例对象所共享,根据上面读取到的内容来确定出需要的内存空间,然后申请内存空间,并把空间内所有的内容都初始化为 0 。

例如,对于 public static int value = 10;,在准备阶段,value 会被初始化为 0,而不是 10。真正的赋值 10 是在初始化阶段进行的。

2.4. 解析

  1. 解析。主要是针对类中的字符串常量进行处理,JVM 将常量池中的符号引用替换为直接引用的过程,也就是初始化常量的过程。

符号引用和内存引用:字符串常量已经在 Class 文件中了,这时是没有“地址”这样的概念的,地址是内存的地址,还没有加载到内存中,这时是用“文件偏移量”来表示的,当加载到内存空间之后就会使用地址来标识,也就是直接引用

2.5. 初始化

  1. 初始化:Java 虚拟机真正开始执⾏类中编写的 Java 程序代码,将主导权移交给应⽤程序。初始
    化阶段就是执⾏类构造器⽅法的过程 。例如创建类的实例,调用类的静态方法,访问类或接口的静态变量(不包括 final 修饰的静态变量,在编译阶段就确定了值),初始化子类时,父类会先被初始化,执行静态代码块。

普通的成员变量(非静态变量)不会在 JVM 类加载过程中的初始化阶段被初始化。类加载过程中的初始化阶段主要是针对类变量(被 static 修饰的变量)以及执行静态代码块。普通成员变量是在对象创建时,即通过构造函数或其他实例初始化方法进行初始化的。当使用 new 关键字创建对象时,会为对象在堆内存中分配空间,并对成员变量进行初始化等操作。

2.6. 双亲委派模型

双亲委派模型是 Java 类加载机制中的一种重要设计模式,也就是上述类加载中的第一步

JVM 默认是有三个类加载器的:

这里的父子关系并不是 Java 中的继承关系,而是类似与指向的一个 parent 引用

双亲委派模型工作原理:

当一个应用程序需要加载一个类时,应用程序类加载器首先会将加载请求委派给它的父类加载器扩展类加载器,扩展类加载器再委派给启动类加载器。如果启动类加载器无法加载该类,扩展类加载器会尝试加载。如果扩展类加载器也无法加载,最后应用程序类加载器才会尝试自己去加载。

那为什么不一开始就给启动类加载器来执行而是又绕了一圈?

  1. 避免类的重复加载:由于父类加载器优先加载,相同的类只会被加载一次,避免了类的重复加载和内存浪费。
  2. 保证核心类库的安全性:防止核心类库被恶意篡改(程序员自己写的类和标准库中的类名字一样,直接从启动类加载器加载会覆盖掉标准库的类),确保了 Java 程序的稳定性和安全性。

就类似与公司中的员工,领导和老板,当员工发现问题是需要上报给领导,领导再上报给老板,如果说这个问题比较重要,老板就亲自解决,如果不是那么重要,但也有一定分量,就交给领导解决,如果只是一些小事,就让员工自己解决

其实类加载器可以程序员自定义,自定义的类加载器可以遵守双亲委派模型也可以不遵守。

3. JVM 的垃圾回收机制(GC)

JVM 的垃圾回收主要是在堆内存进行的,这里存储的都是 new 出来的对象,当这些对象没有引用指向的时候,就成为了垃圾,如果放任不管,就会一直占用着内存,最终造成内存泄露

进行垃圾回收可能会触发 “Stop The World”(STW)问题:在执行垃圾回收的某个阶段,为了保证垃圾回收的准确性和一致性,需要暂停所有应用程序线程,使得整个应用程序出现短暂的停顿。

所以说 C++ 这种追求高性能的语言并没有使用垃圾回收机制

接下来看 JVM 中是如何进行垃圾回收的

首先,进行垃圾回收肯定需要先标记出哪些对象是垃圾

那么怎么去找哪个对象是垃圾呢?

3.1. 识别垃圾

方案一:引用计数。

给每一个对象分配一个计数器,来统计有多少引用指向,每增加一个引用,计数器 + 1,每减少一个引用,计数器 - 1,当计数器减为 0,就变成垃圾了

这个方法看似可行,python 等一些语言采用的就是这个,但是还存在一个问题:

无法解决循环引用的问题。例如,对象 A 和对象 B 相互引用,除此之外没有其他地方引用它们,此时它们的引用计数器都不为 0,但实际上它们已经不再被程序使用,应该被回收。

同时,引入计数器,也会消耗掉额外的空间

方案二:可达性分析

以一系列称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,是垃圾对象,也就是在类似图的结构中从多个根节点遍历一遍,如果发现有的对象没有任何路径可以到达,也就是没有引用指向了,那么就是垃圾

可达性分析有效的解决了循环引用的问题,可以作为根节点的可以是方法区中类静态属性引用的对象(static 修饰的变量引用的对象),常量池中引用的对象,方法中的局部变量引用的对象,本地方法栈引用的对象。

可达性分析由于需要每次都遍历一遍对象,所以是比较消耗时间的

3.2. 垃圾回收的方法

  1. 标记 - 清除法。

在上面遍历的时候标记出哪些是垃圾,然后回收所有被标记的对象所占用的空间,这种方法有很大的缺点:由于每次申请的内存都是一块连续的区域,所以会产生内存碎片,回收后的内存不完整,接下来再申请内存的话可能会申请失败

  1. 复制算法。

将堆空间分为两个大小相等的区域,一般称为 From 区和 To 区。当进行垃圾回收时,将 From 区中仍然被引用的对象(也就是需要继续使用的对象)复制到 To 区,然后清空 From 区。下次垃圾回收时,交换 From 区和 To 区的角色。

这种算法缺点也很明显,就是空间利用率低,一大块区域只有一半能用,同时如果存活下来的对象有很多,复制的成本也比较大

  1. 标记 - 整理算法。

标记阶段就是把哪些对象是垃圾标记出来,接下来整理就是把存活的对象移动到另一端,然后回收边界以外的空间

这种方法的缺点就是移动对象的时候比较消耗时间,特别是对象很多的情况下

3.3. 分代收集算法

根据对象的生命周期将堆空间分为新生代和老年代,也就是在可达性分析的过程中,每经过一轮扫描,对象仍然存活(不是垃圾),年龄就 + 1,新生代中的对象生命周期较短,很多对象短时间内就变成垃圾了,老年代中的对象生命周期较长。

新创建的对象首先在伊甸区分配内存,当伊甸区满时,触发一次新生代垃圾回收,将伊甸区和幸存区 1中仍然存活的对象复制到幸存区 2,然后清空伊甸区和幸存区 1。下次垃圾回收时,交换幸存区 1 和幸存区 2 的角色,继续重复上述操作,幸存区 1 到 幸存区 2 二者的转化会反复进行多次

由于新生代对象大多存活时间少,要复制的也比较少,效率并不会有多大影响,同时空间也没有太大浪费,两个幸存区的空间比较小,也不会有太大空间的浪费。

当一个对象在幸存区反复拷打,年龄不断增加,就进入了老年代,老年代的对象生命周期长,如果还采用复制算法的话,要复制的对象就很多,影响效率,所以就可以采用标记 - 整理算法,需要整理的次数也不多。

上述过程就很好的利用了各个算法的优势来结合,通过分代收集算法,提高了垃圾回收的效率

3.4. 垃圾回收器

JVM 中还提供了多种“垃圾回收器”,对上面介绍的分代回收算法进行了进一步的扩充和具体实现

CMS 垃圾回收器

先大概的进行一个初始标记,然后再通过多线程的方式并发的进行重新标记,标记之后再进行并发的清理,通过这样的方法,能和业务线程并发执行,使 STW 的时间尽可能的缩短

G1 垃圾回收器

G1 是把整个堆内存分层很多小块,进行垃圾回收时,不要求一个周期就把所有的垃圾全都回收,而是一轮周期回收一部分,通过限制每一轮 GC 花的时间在可控范围之内,同时划分为多个区域也是为了更好的使用多线程

在这里插入图片描述

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

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

相关文章

【Linux探索学习】第三弹——Linux的基础指令(下)——开启新篇章的大门

Linux基础指令(上): 【Linux探索学习】第一弹——Linux的基本指令(上)——开启Linux学习第一篇-CSDN博客 Linux基础指令(中): 【Linux探索学习】第二弹——Linux的基础指令&#…

vmstat命令:系统性能监控

一、命令简介 ​vmstat​ 是一种在类 Unix 系统上常用的性能监控工具,它可以报告虚拟内存统计信息,包括进程、内存、分页、块 IO、陷阱(中断)和 CPU 活动等。 ‍ 二、命令参数 2.1 命令格式 vmstat [选项] [ 延迟 [次数] ]2…

18734 拓扑排序

### 思路 1. **建模问题**:将课程和依赖关系建模为有向图,其中课程是节点,依赖关系是有向边。 2. **选择算法**:使用拓扑排序算法来确定课程的学习顺序。由于需要确保输出唯一性,同等条件下编号小的课程排在前面&…

Koa学习

Koa 安装与配置 1. 初始化项目 在终端中执行以下命令: # 创建项目文件夹 mkdir koa cd koa# 初始化并安装依赖 npm init -y npm install koa npm install nodemon --save-dev2. 修改 package.json 在 package.json 文件中进行如下修改: {"type…

LeetCode讲解篇之1143. 最长公共子序列

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题我们可以采用动态规划求解&#xff0c;用一个二维数组记录text1的0 ~ i区间子串和text2的0 ~ j区间子串的最长公共子序列的长度&#xff0c;我们假设该二维数组是f 这个数组有一个特性&#xff0c;如果a <…

ssm服装店销售管理系统

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 摘 要 I Abstract II 第1章 绪论 1 1.1研究背景 1 1.2研究意义 1 1.3国内外研究现状 2 1.3.1国外研…

R语言中的plumber介绍

R语言中的plumber介绍 基本用法常用 API 方法1. GET 方法2. POST 方法3. 带路径参数的 GET 方法 使用 R 对数据进行操作处理 JSON 输入和输出运行 API 的其他选项其他功能 plumber 是个强大的 R 包&#xff0c;用于将 R 代码转换为 Web API&#xff0c;通过使用 plumber&#x…

启动hadoop后没有 NodeManager和 ResourceManager

跟着黑马网课学下去时发现我的hadoop启动后没有NodeManager和ResourceManager 找到日志的路径 我在/export/server/hadoop/etc/hadoop/hadoop-env.sh文件里配置了日志存放的路径 这里找到你的日志路径&#xff0c;每个人的习惯和看的教程不同&#xff0c;日志放的地方大概率也…

MATLAB中lsqminnorm函数用法

目录 语法 说明 示例 求解具有无限个解的线性系统 指定容差以减少含噪数据的影响 切换显示低秩矩阵警告 lsqminnorm函数的功能是线性方程的最小范数最小二乘解。 语法 X lsqminnorm(A,B) X lsqminnorm(A,B,tol) X lsqminnorm(___,rankWarn) 说明 X lsqminnorm(A,B…

【大语言模型-论文精读】用于医疗领域摘要任务的大型语言模型评估综述

【大语言模型-论文精读】用于医疗领域摘要任务的大型语言模型评估综述 论文信息&#xff1a; 用于医疗领域摘要任务的大型语言模型评估&#xff1a;一篇叙述性综述&#xff0c; 文章是由 Emma Croxford , Yanjun Gao 博士 , Nicholas Pellegrino , Karen K. Wong 等人近期合作…

【Arduino IDE安装】Arduino IDE的简介和安装详情

目录 &#x1f31e;1. Arduino IDE概述 &#x1f31e;2. Arduino IDE安装详情 &#x1f30d;2.1 获取安装包 &#x1f30d;2.2 安装详情 &#x1f30d;2.3 配置中文 &#x1f30d;2.4 其他配置 &#x1f31e;1. Arduino IDE概述 Arduino IDE&#xff08;Integrated Deve…

Spring Boot医院管理系统:提升患者体验

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

python pass的作用

class Phone: IMEI None # 序列号 producer “ITCAST” # 厂商 def call_by_4g(self):print("4g通话")class Phone2022(Phone): face_id “10001” # 面部识别ID def call_by_5g(self):print("2022年新功能&#xff1a;5g通话")class NFCReader: nfc_ty…

​​​​​​​如何使用Hugging Face上的FacePoke工具调整照片中人的头部位置

在照片处理中&#xff0c;调整人物的头部位置可以为你带来创意无限的效果。借助Hugging Face上的FacePoke工具&#xff0c;这一操作变得前所未有的简单和高效。以下是详细步骤&#xff0c;教你如何使用FacePoke来调整照片中人的头部位置。 第一步&#xff1a;访问FacePoke工具…

二、Python(项目创建、常见的设置、print函数)

一、项目创建 二、常见设置 1.字体大小 2.插件 3.主题设置 4.配置解释器 三、print函数 在 Python 中&#xff0c;print()函数是一个非常重要的用于输出内容的函数。 作用&#xff1a;print函数在控制台中输出(显示,打印)括号中的内容&#xff0c;可以用于验证代码的输出结果…

【Kubernetes】常见面试题汇总(五十八)

目录 127.创建 PV 失败&#xff1f; 128. pod 无法挂载 PVC&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于【Kubernetes】…

SpringBoot开发——SpringSecurity安全框架17个业务场景案例(一)

文章目录 一、Spring Security 常用应用场景介绍二、Spring Security场景案例1、认证(Authentication)1.1. Spring Security 配置1.2 业务逻辑代码1.3 登录控制器1.4 登录页面 (login.html)2、授权(Authorization)2.1 Spring Security 配置2.2 业务逻辑代码2.3 控制器3、表…

如何判断静态代理IP地址是否被污染?

在网络使用中&#xff0c;静态IP代理是一种常见的工具&#xff0c;用于维持稳定的连接和保护个人隐私。然而&#xff0c;有时这些IP地址可能会被污染&#xff0c;导致用户遭受各种问题&#xff0c;如连接延迟、数据泄露等。因此&#xff0c;了解如何判断址是否被污染至关重要。…

EPC User Manual Introduction

Overview 您提供的链接是指向srsRAN 4G项目的官方文档&#xff0c;具体是关于srsEPC的介绍部分。以下是该页面的核心内容概要&#xff1a; ### 概述 srsEPC是一个轻量级的完整LTE核心网络&#xff08;EPC&#xff09;实现。srsEPC应用程序作为一个单一的二进制文件运行&#…

【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解

高阶数据结构相关知识点可以通过点击以下链接进行学习一起加油&#xff01;二叉搜索树 大家好&#xff0c;这里是店小二&#xff01;今天我们将深入探讨高阶数据结构中的AVL树。AVL树是一种自平衡的二叉搜索树&#xff0c;可以看作是对传统二叉搜索树的优化版本。如果你对数据结…