Java虚拟机 - JVM

JVM的内存区域划分

JVM它其实也是一个进程,进程运行的过程中,会从操作系统中申请一些资源.内存就是其中的一种.这些内存就支撑了java程序的运行.JVM从系统中申请的一大块内存,会根据实际情况和使用用途来划分出不同的空间,这个就是区域划分.它一般分为 堆区, 栈区, 程序计数器, 元数据区4个区域.

堆区

我们代码中new出来的对象就是在堆当中,对象持有的非静态成员变量,也是在堆当中.

栈区

栈区分为本地方法栈和虚拟机栈. 本地方法栈是jvm通过c++写的代码的调用关系和局部变量.一般我们只关心虚拟机栈. 虚拟机栈是记录了java代码的调用关系和java代码的局部变量.

程序计数器

这是一块小区域,专门用来存储下一条要执行的java指令的地址,和x86cpu中的eip寄存器差不多.

元数据区

在之前也叫做 "方法区". 它里面记录的是类的信息,方法的信息, 一个程序有哪些类,每个类有哪些方法,方法里面都会包含哪些指令,都会记录在元数据区中.想我们的代码中的各种逻辑都会转化为java字节码,这些字节码就会在程序运行的时候被jvm加载到内存中,放到方法区当中.程序怎么执行就会按照元数据区中的java字节码来依次执行.

注意: 这里堆和元数据区只有一份,但是栈区和程序计数器由N份,和线程数目相关.

JVM的类加载机制

这里的类加载就是java进程来运行的时候,需要把.class文件从硬盘读取到内存,且进行一系列的校验解析的过程. 这里加载的过程可以分为5个步骤. 加载 - 验证 - 准备 - 解析 - 初始化.

加载

大概就是把硬盘中的.class文件找到,打开文件且读取到文件内容.

验证

把当前读到的文件内容需要确保是合法的.class文件格式.

准备

给类对象申请内存空间.这里的申请到的内存空间,里面的默认值都是0.类对象中的静态成员变量的值也就是0)

解析

这里就是对类中的字符串常量进行处理.将常量池中的符号引用转换为直接引用的过程.(符号引用可以理解为字符串和它的引用在文件中有偏移量,直接引用则就是地址).

初始化

针对类对象完成后续的初始化过程(这里还会执行静态代码块的逻辑,父类的加载)

双亲委派模型(加载环节)

这里描述了怎么样来查找.class文件的策略.在我们的JVM中进行类加载的操作,会有一个专门的模块 - "类加载器(ClassLoader)". JVM中有三个ClassLoader.这里类加载器的作用就是给它一个带有包名的类名,来找到对应的.class文件.

三个库的作用:

BootstrapClassLoader: 负责查找标准库中的目录.

ExtensionClassLoader: 负责查找扩展库中的目录.

ApplicationClassLoader: 负责查找当前项目的代码目录和第三方库.      

这三个加载器的关系类似于父子关系.

工作过程

1. 先以ApplicationClassLoader为入口,开始工作.,但是它不会立刻开始查找自己负责的目录,而是将任务交给它的父亲.

2. 任务到ExtensionClassLoader,它也不会立刻开展工作,而是交给它的父亲.

3. 任务到BootsstrapClassLoader,它才会开始真正的搜索负责的目录.通过全限定类名,来尝试在标准库中找到符合的.class文件.找到了就进入打开文件,读文件的流程.没找到则交给它的孩子来处理.

4. ExtensionClassLoader拿到父亲给它的任务后,也会通过全限定类名,在扩展库中查找符合的.class文件.找到了就进入读文件的流程.没找到再交给它的孩子处理.

5. ApplicationClassLoader拿到夫妻给它的任务后,也会通过全限定类名,在项目中的目录和第三方库的目录中查找.找到了就进入读文件流程.没找到说明加载失败.则会抛出ClassNotFoundException异常.

这样设定的目的其实最主要的就是确保这几个类加载器的优先级. 按照这样的方式就算你自己写的类不小心和标准库中的类名字重复了,也可以避免导致标准库中的类失效.

JVM的垃圾回收机制(GC)

JVM的垃圾回收,说的就是将内存回收.在JVM的多个区中. 程序计数器不需要GC,栈不需要,因为它的局部变量在代码块执行结束后就会进行自动销毁. 元数据区也不需要,它一般都是涉及到类加载,很少涉及到类卸载. 这里最主要使用GC的就是堆区.因为它里面记录的是对象,而代码中可能会new出许许多多的对象,有的对象可能不使用了,就需要销毁.所以这里的内存回收,也可以看成是对象回收.

垃圾回收分为两步:

1. 识别出垃圾,哪些对象是垃圾,哪些对象不是垃圾.

2. 把标记为垃圾的对象的内存空间进行释放.

识别垃圾

这里识别一个对象是不是需要继续使用,就是判断还有没有引用在指向它.如果没有,它就可以视为垃圾.这里有两种方法: 1. 引用计数 2. 可达性分析

引用计数

这个方法在JVM中并没有使用,但是在其他的主流语言中还是在广泛应用中.

它的处理方式就是:

给每一个对象安排一个额外的空间,这个空间里就保存当前有几个引用. 而另一边会有专门的扫描线程,去获取到当前每个对象的引用计数的情况,一但发现这个对象的引用计数为0,就代表可以释放了.

但是它会存在一些问题:

1. 会消耗额外的内存空间.尤其是当对象比较小时,你的计数器消耗的空间可能就达到对象的一半了.

2.引用计数可能会产生循环引用的问题.

可达性分析(JVM使用的方法)

它的本质就是用时间来换空间. 它就不会产生额外的空间和循环引用的问题.

在代码中,会定义出很多变量,栈上的局部变量,堆上的成员变量,方法区的静态变量,常量池中引用的对象... 它就可以从这些变量作为起点,去进行遍历.这个遍历就是沿着这些变量持有的引用类型的成员,再进行下一步的访问. 可以遍历到的对象就不是垃圾,反之.

这里的遍历我们可以想象为二叉树的遍历.一但有一个节点为null,那这个结点后面的也都为null,也就是垃圾.

JVM中会存在扫描线程,对代码不断进行遍历.

清理垃圾

清理垃圾就是将被标记的内存空间进行释放.这里释放有三种方法.

标记 - 清除

这里就是直接将标记为垃圾的对象直接释放掉.但是一般不会使用这个方案.因为它会有内存碎片问题.直接释放,就会产生很多小的,离散的空闲内存空间,这就可能导致后续的内存申请失败.因为内存申请一般都是申请一块连续,大范围的空间.

复制

它的核心就不是直接释放内存,也是将不是垃圾的对象,复制到另一半的空间中(它会将可用内存空间分为两半). 

这样子就规避了内存碎片化的问题,但是也会有一些缺点.

1. 总用的内存空间变少了.

2. 如果每次需要复制的对象比较多,复制开销就会很大了(它适合大部分对象都释放,少部分对象存活,这个时候使用复制)

标记 - 整理

这个方式就类似于顺序表中的删除中间元素,需要进行搬运.通过这个过程,也就解决了内存碎片的问题,但是它这里的搬运内存开销会很大.

JVM的分代回收

 因为上面方法的优缺点,JVM就结合上面的思想,搞出来了一个综合方案.

JVM会给对象进行年龄记录,被JVM的扫描线程扫描过一次后就加一岁,起始岁数为0. 而还给堆内存划分了两个区域:分为为新生代和老年代.且新生代中还分了三个区:伊甸区和两个生存区.

 处理流程:

1. 当代码中new出一个新的对象时,就是创建在伊甸区中的,伊甸区中会有很多对象.(放在里面是有一个经验规矩: 伊甸区的对象大多都是活不过第一轮GC的,"朝生夕死")

2. 当第一轮GC后,少部分存活下来的对象,就会通过复制算法拷贝到生存区中(GC后存活下来的对象不会很多,复制开销也不会很大).后续还会有GC扫描,不仅会扫描伊甸区,还会扫描生存区.生存区的大多数对象也会在扫描中被标记为垃圾.少部分存活的,又会继续复制算法拷贝到另一个生存区汇中.这样不断往复,且每过一次GC,生存下来的对象年龄就会+1.

3. 当一个对象在生存区中经历了若干次GC还没有消亡,则JVM就会认为它的生命周期特别长,就会将它从生存区拷贝到老年代.

4. 老年代的对象也会被GC扫描,但是扫描频率会大大降低.

5.当老年代的对象消亡时,JVM就会按照标记整理的方式,释放内存(老年代的对象很少,所以搬运开销不会很大)


到这里,上面的就是JVM中GC的核心思想.但是在一些实现细节上还是会有一些变数和优化~

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

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

相关文章

10分钟快速入门UI自动化-Puppeteer

这次带大家入门的是转转内部实现UI自动化测试的一种方案: PuppeteerMocha 目前应用于转转图书、奢侈品、商业等业务等多个Web/H5业务的线上流程监控; 先简单介绍一下: Mocha 是JavaScript的一种单元测试框架 Puppeteer 是一个 Node 库&…

Spring核心接口:BeanFactory接口

一图胜千言 BeanFactory 属性&方法解析 点击展开注意:以上代码仅供参考,可能存在不完整或不准确的情况。 public interface BeanFactory {// 根据Bean名称返回Bean实例。// 如果Bean不存在,则抛出NoSuchBeanDefinitionException异常。Obj…

【Linux系统编程】进程的退出与等待

进程的创建 fork()用于创建子进程。但fork创建的子进程获得的是父进程(即调用 fork() 的进程)的一份几乎完全相同的副本,包括父进程的代码、数据、堆、栈和数据结构等内容。当进程调用fork后,一旦控制转移到内核中的fork代码后&am…

SpringBoot(接受参数相关注解)

文章目录 1.基本介绍2.PathVariable 路径参数获取信息1.代码实例1.index.html2.ParameterController.java3.测试 2.细节说明 3.RequestHeader 请求头获取信息1.代码实例1.index.html2.ParameterController.java3.测试 2.细节说明 4.RequestParameter 请求获取参数信息1.代码实例…

ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

发生的错误信息: File "C:\Users\malongqiang\.conda\envs\ObjectDetection\lib\ssl.py", line 1309, in do_handshakeself._sslobj.do_handshake() ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。 分析原因: …

App Inventor 2 Clipboard 拓展:实现剪贴板的复制粘贴功能

效果如下: 此 Clipboard 拓展由中文网开发及维护,最新版本 v1.0,基于 TaifunClipboard 开发。 使用方法 属性及方法很简单,默认操作成功后显示提示信息,SuppressToast设置为 假 后,则不显示提示信息。 经测…

k8s部署InfluxDB

(作者:陈玓玏) 1. 拉取镜像 docker pull influxdb #拉取镜像 docker run -d influxdb:latest #后台运行容器 docker exec -it 89b /bin/bash #进入容器,89b是容器ID的前三位 cd /usr/bin #进入容器后,进入此文件夹…

计数类Dp

文章目录 AcWing 900. 整数划分思路1. 完全背包AC CODE 2. 计数DpAC CODE AcWing 900. 整数划分 链接:https://www.acwing.com/activity/content/problem/content/1008/ 思路 1. 完全背包 完全背包的链接:https://blog.csdn.net/2301_78981471/artic…

【原创】EPLAN中端子图表的制作

在上一期的文章中,我们主要谈了EPLAN制图中常用符号,有伙伴提出,让我们写一篇,关于端子图表的技术资料。我们这篇文章就是作为对伙伴评论的回复。 在EPLAN制图的过程中,我们一起探讨技术问题,共同解决我们现实工作中遇到的问题,欢迎大家在评论区留言,我们会根据大家的…

IPFoxy的正确打开方式

IPFoxy是一个全球动静态代理IP服务器软件,为全球用户提供优质的大数据代理服务,促进网络业务高校进行。目前拥有千万真实纯净IP资源,覆盖超过220个国家和地区,汇聚成优质海外代理池,支持http、https、socks5多种协议类…

人机交互三原则,网络7层和对应的设备、公钥私钥

人机交互三原则 heo Mandel提出了人机交互的三个黄金原则,它们强调了相似的设计目标,分别是: 简单总结为:控负持面–>空腹吃面 1,用户控制 2,减轻负担 3,保持界面一致 置用户于控制之下&a…

2024/3/14 定时器时钟

感觉和之前自己写的那个差不太多&#xff0c;只是用了定时器。贴一下代码. main.c #include <REGX52.H> #include "LCD1602.h" #include "Delay.h" #include "Timer0.h" unsigned char Sec59,Min49,Hour17;void main() {LCD_Init();Time…