【JVM】内存区域划分 | 类加载的过程 | 双亲委派机制 | 垃圾回收机制

news2024/11/19 7:33:17

文章目录

  • JVM
    • 一、内存区域划分
          • 1.方法区(1.7之前)/ 元数据区(1.8开始)
          • 2.堆
          • 3.栈
          • 4.程序计数器
          • 常见面试题:
    • 二、类加载的过程
      • 1.类加载的基本流程
          • 1.加载
          • 2.验证
          • 3.准备
          • 4.解析
          • 5.初始化
      • 2.双亲委派模型
            • 类加载器
            • 找.class文件的过程:
            • 打破双亲委派模型
    • 三、垃圾回收机制
            • GC的缺陷
            • GC回收的目标
        • 回收的步骤
          • 1.找到垃圾
            • 1.引用计数 [Python 、PHP]
            • 2.可达性分析 [Java]
            • GCRoots
          • 2.释放垃圾
            • 1.标记清除
            • 2.复制算法
            • 3.标记整理
            • 分代回收

JVM


一、内存区域划分

​ 一个运行起来的Java进程,就是一个JVM虚拟机。会从操作系统申请一大块内存。这块内存会被划分成不同的区域,每个区域都有不同的作用。

类似于租了一个写字楼,进行装修,划分不同的功能

1.方法区(1.7之前)/ 元数据区(1.8开始)
  • 存储的内容是类对象

类对象:.class文件,加载到内存之后,就成了类对象

2.堆
  • 存储的是代码中new的对象

  • 堆是这块空间中,占据空间最大的区域

3.栈

虚拟机栈

  • 存储的是代码执行过程中,方法之间的调用关系

  • 栈中的每个元素称为“栈帧”。栈帧就代表了一个方法调用。栈帧里包含了方法的入口、方法返回的位置、方法的形参、方法的返回值、局部变量…

4.程序计数器
  • 相对比较小的空间
  • 存放一个“地址”。表示每个线程,下一条要执行指令的地址。这个执行的指令在方法区里(每个方法,里面的指令,都是以二进制的形式,保存到对应的类对象中)
class Test{
	public void a(){
		//
	}
	public void b(){
		//
	}
}

​ 这个类中有两个方法。方法a和方法b都会被编译成二进制的指令,放到.class文件中。在执行类加载的时候,就会把.class文件里的内容,加载起来,放到类对象里。此时方法的二进制指令也就进入类对象了。

​ 刚开始调用方法时,程序计数器记录的是方法的入口地址。随着一条一条的执行指令,每执行一条,程序计数器的值都会自动更新,去指向下一条指令。如果是顺序执行的代码,下一条指令就是把指令地址进行递增。如果是条件/循环代码,下一条指令就可能会跳转到比较远的地址。

  • 每个线程都有一份虚拟机栈和程序计数器
  • 每个进程只有一份堆和元数据区
常见面试题:

给一段代码,说明某个变量,是处于JVM内存当中的哪个区域

class Test{
    public int n;
    public static int a = 10;
}
void main(){
    Test t = new Test();
}

请问:n、a、t 分别处于哪个区域?

答:1.n是一个成员变量,在new Test对象的时候,这个对象中就会包含n这个属性。new出来的对象在堆上,因此成员变量n就处于堆上。2. t是方法内部的一个局部变量,处于栈上。每个栈帧包含有一个局部变量表,通过局部变量表来保存局部变量。3.a是一个静态变量,也称作类属性。包含在类对象中,处于方法区/元数据区当中。

变量处于哪个空间上,与变量是引用类型还是基本类型无关。t这个变量是一个引用类型的变量,存的是一个对象的地址,而不是对象本身。

二、类加载的过程

1.类加载的基本流程

​ Java代码会被编译成.class文件(包含了一些字节码)。Java程序要想运行起来,就需要让JVM读取到这些这些.class文件,并且把里面的内容构造成类对象,保存到内存的方法区中。

​ “执行代码”就是调用方法,需要先知道每个方法,编译后生成的指令都是啥。所以先将.class文件中的指令,先读到内存中,构造成类对象。程序计数器指向类对象中对应方法的具体指令。JVM就会根据指令的位置继续执行。

1.加载
  • 找到.class文件,打开文件并读取文件内容。

​ 代码中,会给定某个类的“全限定类名”(带有包名的,例如java.long.String/java.util.ArrayList)JVM就会根据这个类名,在一些指定的目录范围内,进行查找。

2.验证

​ .class文件是一个二进制的格式,某个字节都有某个特殊含义。需要验证当前读到的这个文件格式是否符合要求。(.class文件的内容格式要符合java设定的规范)

在这里插入图片描述

java有具体的语言规范和虚拟机规范,虚拟机规范中,规定了.class文件要遵循的格式结构

在这里插入图片描述

  • 一般二进制文件,开头的几个字节都是固定的数字,用来表示文件的格式。这个数字称为magic number “魔幻数字”
  • 验证就是要确保读到的.class文件,当中的格式,是严格按照上述内容展开的。如果验证失败就会返回报错
3.准备
  • 因为类加载的目的就是构造出一个类对象,所以准备这一步,就是要给类对象分配内存空间。

​ 这里只是分配内存空间,还没有进行初始化。此时内存中存储的对应数据都是0(此时打印这个类中的static成员,就都是0)

4.解析
  • 处理类对象中包含的字符串常量。进行一些初始化操作,用真正的内存地址来替换偏移量

java代码中用到的字符串常量,在编译后,也会进入到.class文件中。

final String s = "test";
//'test'作为字符串常量,会进入到.class文件当中
//通时,.class文件的二进制指令中,也会创建出一个s这样的引用

​ 由于引用的本质是保存一个变量的地址。.class文件,不涉及内存地址。所以在.class文件中,s的初始化语句会先被设置成一个“文件的偏移量”。通过这个偏移量,就可以找到"test"字符串所在的位置。当这个类真正被加载带内存中时,再把偏移量替换回真正的内存地址

在这里插入图片描述

  • 把“符号引用”(文件偏移量)替换成"直接引用"(内存地址)
5.初始化
  • 针对类对象进行初始化

把类对象中需要的各个属性都设置好,还需要初始化static成员,执行静态代码块,加载父类

2.双亲委派模型

  • 双亲委派模型是类加载中,“加载”过程中的一个环节。负责根据“全限定类名”来找到.class文件。
类加载器

类加载器是JVM的一个模块。JVM中内置了三个类加载器:

1.BootStrap ClassLoader (爷)

2.Extension ClassLoader (父)

3.Application ClassLoader (子)

这些类加载器中有一个parent属性,指向父"类加载器"

“双亲”指的就是parent这个属性

找.class文件的过程:

1.给定一个类的全限定类名,(java.long.String,)

2.从Application ClassLoader 作为入口,开始执行查找的逻辑。

3.Application ClassLoader ,不会立即扫描自己负责的目录(负责的是搜索项目当前目录和对应的第三方库目录),而是把扫描的任务交给它的父亲(Extension ClassLoader)

4.Extension ClassLoader,也不会立即扫描自己负责的目录(负责的是JDK中一些扩展的库,对应的目录),把查找的任务交给它的父亲(BootStrap ClassLoader)

5.BootStrap ClassLoader,也不会立刻扫描自己负责的目录(负责的是 标准库的目录),也想交给父亲来扫描,但是由于没有父亲,就只能自己亲自扫描 标准库的目录。java.long.String这个类就能在标准库中,找到对应的.class文件,进而打开读取文件

6.如果没有扫描到,就会返回到Extension ClassLoader。负责扫描扩展库的目录 。找的了后续的类加载

7.如果没有扫描到,就会返回到Application ClassLoader。负责扫描当前项目和第三方库的目录,找的了进行后续类加载

8.最终如果没有找到,也没有孩子了,就会抛出一个ClassNotFoundException 异常。

  • 这样做的目的,是为了确保标准库的类优先级最高,其次的扩展库,其次是自己写的类和第三方库
打破双亲委派模型
  • 自己写的类加载器,就可以不遵守这些规则。tomcat里,加载webapp的时候就是用的自定义加载器,就只能在webapp指定目录中查找,找不到就直接抛出,不会去标准库中去找。

三、垃圾回收机制

  • GC 垃圾回收

​ 在C语言中,用malloc进行“动态申请内存”,用完后通过free释放。C++里则用new动态申请内存,用完后通过delete来释放。malloc只是申请内存,new不仅能申请内存,也能进行初始化(调用构造函数)。Java也采用了new这样的写法,在Java中new一个对象,就是“动态申请内存”。

  • Java通过垃圾回收机制(GC),来让JVM自行判断,某个内存是否不再使用。如果后面不用了,就会自动把这个内存回收掉,从而不需要手动写代码回收
GC的缺陷

1.系统开销,需要一些特定的线程,不断扫描内存中的所有对象,看是否能够回收。需要额外的cpu资源

2.效率问题,扫描线程有一定周期,不一定能及时释放内存。一旦有大量对象需要被回收,GC的负担会变得很大,从而引发程序的卡顿(STW问题 stop the world)

GC回收的目标

​ 目标是内存中的对象。对应Java来说,就是new出来的这些对象。栈里的局部变量,是跟随着栈帧的生命周期走的(方法执行结束,栈帧销毁,内存自然释放)。静态变量,生命周期是整个程序。不需要进行释放。真正需要GC释放的,是堆上new出来的对象。

回收的步骤
1.找到垃圾

这里的“垃圾”指的是不再使用的对象。有两种主要方案

1.引用计数 [Python 、PHP]

​ new出来的对象,单独安排一块空间,来保存一个计数器。用来描述这个对象被几个引用所指向。如果一个对象没有被引用指向(引用计数是0.)就可以被视为“垃圾”

引用计数的缺点:

1.比较浪费内存。

​ 每个对象丢需要有一个计数器。计数器会占据不小的空间,如果对象本身很小并且数量很多。计数器占用的空间比例的无法忽视。

2.存在“循环引用”的问题

//形如以下代码、
class Test{
    public Test t;
}
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;

16730357914)

​ 当a和b两个引用已经被销毁了。new出来的这两个对象无法被其他代码访问到了,但是他们的引用计数却不是0,所有不能进行回收。第一个对象引用了第二个对象,第二个对象引用了第一个对象。要想使用第一个对象就需要拿到第二个对象,要想拿到第二个对象,又得先拿到第一个对象。构成了“循环引用”

2.可达性分析 [Java]
  • 本质上是 时间换取空间的手段

  • 有一个/一组 线程。周期性的扫描代码中所有的对象。从一些特定的对象出发,尽可能的进行访问的遍历。把所有能够访问到的对象都标记成“可达”。反之,扫描后没有被标记的对象,就是“垃圾”。

void func(){
    TreeNode root = bulidTree();
}

  • 就相当于从根节点root这个引用出发,不断遍历,到达整棵树的左右节点。能遍历到的TreeNode对象,都是可达的。
GCRoots

​ 可达性分析的出发点有很多,不仅是所有的局部变量,还有常量池中引用的对象、还有方法区中的静态引用类型引用的变量…这些出发点就叫做GCRoots。

​ 这里的遍历大概率是N叉树,取决于访问是对象里有多少个引用类型的成员,针对每个引用类型的成员都需要进一步进行遍历。对象是否为垃圾,可能会随着代码的执行而发生改变,所以扫描过程是周期性进行的。这样下来,可达性分析就比较消耗系统资源,开销就比较大。

2.释放垃圾

有三种主要方案

1.标记清除

​ 比较简单粗暴的释放方式。

​ 经过可达性分析后,找到了“垃圾”对象,直接释放垃圾对象对应的内存。但是这样做会产生很多内存碎片。释放内存的目的是为了让别的代码能够申请。申请内存都是申请“连续”的内存空间。

2.复制算法

在这里插入图片描述

​ 通过复制的方式,把有效的对象,归类到一起,再统一释放剩下的空间

把内存分成两份,一次只用其中的一半。从而有效解决内存碎片问题。

缺点:

​ 1.内存浪费了一半,利用率不高

​ 2.如果有效对象比较多,拷贝的开销就很大

3.标记整理
  • 既能解决内存碎片问题,又能处理复制算法中利用率的问题

在这里插入图片描述

  • 类似于顺序表删除元素的操作,搬运的开销仍然很大

实际上,JVM采取的释放思路,是上述三种思路的结合体。

分代回收

在这里插入图片描述

  • 伊甸区:存放刚new出来的对象。从对象诞生到第一轮可达性分析扫描,这个过程中(毫秒~秒级)大部分对象都会成为垃圾。(创建的对象,指向对象的引用很快就会随着方法执行完毕而消亡。就会变成垃圾)
  • 幸存区:第一轮结束后,仍然不是垃圾的对象,就会被“复制算法”,拷贝到幸存区

1.伊甸区=>幸存区 复制算法的体现,每一轮GC扫描之后,都把有效对象复制到幸存区中(真正需要拷贝的并不多),伊甸区就可以整个释放了

2.GC扫描线程也会扫描幸存区,把扫描后“可达”的对象,拷贝到幸存区的另一部分。(幸存区分成两部分,也是复制算法 的体现)

3.当对象已经在幸存区存活过很多轮GC扫描之后,JVM就认为这个对象在短时间内应该不会释放,就会把这个对象拷贝到老年代。

4.进入老年代的对象,虽然也会被GC扫描,但是被扫描的频率要比新生代要低很多。 老年代相对生命周期更长,所以降低扫描频率,减少GC扫描的开销。在老年代中,使用标记整理的方式进行回收

点击移步博客主页,欢迎光临~

偷cyk的图

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

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

相关文章

智能无网远控再升级 向日葵Q2Pro升级版发布

无网或者内网设备也想要进行远程控制,是不是听上去有些天方夜谭了?其实这类特种设备的远程控制需求是非常强的,比如医疗/工控设备的远程运维、使用指导教学等等。 实际上,只要这类设备有屏幕,支持可视化的桌面操作&am…

JVM学习-Class文件结构①

字节码文件的跨平台性 Java语言:跨平台的语言(Write Once,Run Anywhere) 当Java源代码编译成字节码后,如果想在不同平台上运行,则无须再次编译这上优势不再那么吸引人,Python,PHP,Ruby,Lisp等有强大的解释器跨平台似乎已经成为一…

c语言:模拟strlen(三种方法)最全版本

1.计数的方法 #include <stdio.h> #include <assert.h> int my_strlen(const char * str)//const的使用优化 {int count0;assert(str)while(*str){count;str;}return count; } 2.用指针的方法&#xff08;指针-指针&#xff09; #include <stdio.h> #incl…

ML307R OpenCPU 数据保存文件系统fs使用

一、函数介绍 二、实现数据保存 三、代码下载地址 一、函数介绍 以下是cm_fs.h里面的函数介绍 /*** brief 文件指针定位** param [in] fd 文件描述符* param [in] offset 指针偏移量* param [in] base 偏移起始点&#xff0c;CM_FS_SEEK_SET&#xff1a;文件开头 CM_FS…

零基础学Java第二十三天之网络编程Ⅱ

1. InetAddress类 用来表示主机的信息 练习&#xff1a; C:\Windows\system32\drivers\etc\ hosts 一个主机可以放多个个人网站 www.baidu.com/14.215.177.37 www.baidu.com/14.215.177.38 www.taobao.com/183.61.241.252 www.taobao.com/121.14.89.253 2. Socket 3.…

vue3和vite实现vue-router4版本路由的配置以及自动生成路由配置

这个是普通的手动路由配置&#xff1a;https://blog.csdn.net/weixin_68658847/article/details/130071101 自动路由配置 创建项目 npm create vitelatest my-vue-app -- --template vue // 或者 yarn create vite my-vue-app --template vue// 安装路由 yarn add vue-route…

WAF几种代理模式详解

WAF简介 WAF的具体作用就是检测web应用中特定的应用&#xff0c;针对web应用的漏洞进行安全防护&#xff0c;阻止如SQL注入&#xff0c;XSS&#xff0c;跨脚本网站攻击等 正向代理 WAF和客户端与网络资源服务器都建立连接&#xff0c;但是WAF 的工作口具有自己的 IP 地址&…

vscode 插件-01基础

翻译 Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code 适用于 VS Code 的中文&#xff08;简体&#xff09;语言包 远程连接 Remote Development Remote Development是vscode的远程编程与调试的插件&#xff0c;使用这个插件可以在很多情况下代替vim…

CentOS 的常见命令

CentOS 是一种广泛使用的 Linux 发行版&#xff0c;特别在服务器环境中。本文将详细介绍 CentOS 中常见的命令&#xff0c;以便帮助用户在操作系统中有效地进行各种操作。下面介绍一下文件和目录操作、用户和权限管理、系统信息查看、软件包管理以及网络配置等方面的命令。 一…

Java 类加载和实例化对象的过程

1. 类加载实例化过程 当我们编写完一个*.java类后。编译器&#xff08;如javac&#xff09;会将其转化为字节码。转化的字节码存储在.class后缀的文件中&#xff08;.class 二进制文件&#xff09;。接下来在类的加载过程中虚拟机JVM利用ClassLoader读取该.class文件将其中的字…

JavaEE初阶多线程 (5)

1.锁的策略 1.1锁的策略是什么 这个锁的策略可以理解为&#xff0c;一种做法&#xff0c;相当于当你遇到锁竞争&#xff0c;加锁解锁&#xff0c;的情况你会怎么做。 乐观锁可以理解为疫情的时候比较乐观就买了最基本的物资&#xff0c; 买的时候非常方便 1.2乐观锁 当效率…

linux的用户管理

新建用户&#xff1a;1.useradd 2.passwd 完成的操作&#xff1a; (1)/etc/passwd添加一行 (2)/etc/shadow添加一行 (3)/etc/group添加一行 (4)创建用户家目录 (5)创建用户邮件文件 例&#xff1a;创建用户jerry&#xff0c;要求: uid:777&am…

Linux系统进程管理

系统进程管理 一、进程概述 1.1 什么是进程&#xff1f;进程管理需要做什么&#xff1f; 进程是已启动的运行实例&#xff0c;进程有以下组成部分&#xff1a; ​ 已分配内存的地址空间 ​ 进程ID ​ 程序的代码 ​ 进程状态 进程管理包括进程调度、中断处理、信号、进程…

基于 vuestic-ui 实战教程

1. 前言简介 Vuestic UI是一个基于开源Vue 3的UI框架。它是一个MIT许可的UI框架&#xff0c;提供了易于配置的现成前端组件&#xff0c;并加快了响应式和快速加载Web界面的开发。它最初于2021年5月由EpicMax发布&#xff0c;这就是今天的Vuestic UI。 官网地址请点击访问 体验…

半年不在csdn写博客,总结一下这半年的学习经历,coderfun的一些碎碎念.

前言 自从自己建站一来&#xff0c;就不在csdn写博客了&#xff0c;但是后来自己的网站因为资金问题不能继续维护下去&#xff0c;所以便放弃了自建博客网站来写博客&#xff0c;等到以后找到稳定&#xff0c;打算满意的工作再来做自己的博客网站。此篇博客用来记录自己在csdn…

SCI审稿结果也可能是剽窃的?Nature重锤:可能只是冰山一角

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 近期&#xff0c;Nature报道了华沙生命科学大学水文学家Mikołaj Piniewski和他的同事们&#xff0c;针对学术界同行评审报告剽窃的调查结果。该团队披露了数十起明显的同行评…

Android单元测试实践

一、基础概念 按照Google官方建议,Android测试体系应该参照测试金字塔架构(如下图所示),App应该包含三类测试(即小型、中型和大型测试)。 图片 小型测试是指单元测试,用于验证应用的行为,一次验证一个类。中型测试是指集成测试,用于验证模块内堆栈级别之间的交互或相…

CHI协议_1

作者&#xff1a;someone链接&#xff1a;https://www.zhihu.com/question/304259901/answer/3455648666来源。 1. AMBA CHI简介 一致性总线接口&#xff08;CHI&#xff09;是AXI一致性扩展&#xff08;ACE&#xff09;协议的演进。它是Arm的AMBA总线的一部分。AMBA是一种免…

【数据结构/C语言】深入理解 双向链表

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法 在阅读本篇文章之前&#xff0c;您可能需要用到这篇关于单链表详细介绍的文章 【数据结构/C语言】深入理解 单链表…

博客开始使用 Cache Master 缓存插件

明月在给大家推荐 Cache Master 插件的时候&#xff08;可参考【推荐个比较纯正的缓存插件——Cache Master】一文&#xff09;&#xff0c;仅仅是在其他站点上试用了一下&#xff0c;今天明月正式在博客上用上了 Cache Master&#xff0c;没有想到对 Dragon 主题的支持竟然是出…