JVM类加载的过程和JVM垃圾回收机制

文章目录

  • 一、JVM类加载的过程
    • 1.1类加载的基本流程
      • 1.1.1加载
      • 1.1.2验证
      • 1.1.3准备
      • 1.1.4解析
      • 1.1.5初始化
    • 1.2双亲委派模型
  • 二、JVM垃圾回收机制
    • 2.1找到垃圾
      • 2.1.1引用计数(比如Python,PHP中用到)
      • 2.1.2可达性分析(比如Java中用到)
    • 2.2释放垃圾
      • 2.2.1标记清除
      • 2.2.2复制算法
      • 2.2.3标记整理
      • 2.2.4分代回收

一、JVM类加载的过程

1.1类加载的基本流程

Java代码会被编译成.class文件(里面包含了一些字节码),JVM会把.class文件读取到内存中并对其进行解析、构造类对象(这个过程叫类加载),类加载完成之后就会在内存中得到类对象,后续要构造这个类的实例都是基于类对象来进行展开的。

1.1.1加载

找到.class文件,打开文件,读取文件内容。从Java代码中往往会得到某个类的“全限定类名”(比如java.lang.String),JVM会根据这个“全限定类名”在一些指定的目录范围内去查找对应的.class文件,找到对应的.class文件就能够把这个.class文件打开并且读取里面的内容。

1.1.2验证

验证.class文件里的内容是否符合要求。
.class文件是二进制格式的文件,里面的某个字节都是有某些特定含义的。java标准文档:https://docs.oracle.com/javase/specs/index.html里说明了一个.class文件的格式是怎样的,.class文件里应该要包含哪些内容。

1.1.3准备

给类对象分配内存空间。这个内存空间的大小是根据上一步的验证的结果来确定的。这里只是分配内存空间,还没有初始化内存空间,此时这个内存空间上的数值全是0,此时如果打印类的static成员就会打印出0。

1.1.4解析

针对类对象中包含的字符串常量进行一些初始化操作。

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

比如java代码中有:final String a = “hello”;
编译之后,.class文件的二进制指令中也会有一个a这样的引用被创建出来,由于引用本质上保存的是一个变量的地址,在.class文件中,因为文件不涉及到内存地址,所以.class文件中的a就会先被设置成一个“文件偏移量”,通过这个“文件偏移量”可以找到hello这个字符串所在的位置,当我们把这个类真正加载到内存的时候,再把这个“文件偏移量”替换回真正的hello的内存地址。

在这里插入图片描述
如上图所示,假设在.class文件中,文件开头到hello开头的距离是100个字节,就称hello这个字符串在.class文件中的“文件偏移量”为100。文件开头到test开头的这100个字节里也会有一条指令,这条指令描述了String a = @100,这里的@100表示“文件偏移量”。当.class文件加载到内存中的时候,test这时的内存地址为0x12,String s = @100也会把@100这个“文件偏移量”替换成hello这个字符串真实的内存地址,这个替换的过程就是“解析”阶段要完成的主要工作。这个替换过程也叫把“符号引用”(“文件偏移量”)替换成“直接引用”(内存地址)。

1.1.5初始化

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

1.2双亲委派模型

双亲委派模型属于类加载的第一个步骤“加载”过程中的其中一个环节,即根据“全限定类名”找到.class文件。

JVM中内置了三个类加载器(程序员也可以手动创建出新的类加载器):
①BootStrap ClassLoader
②Extension ClassLoader
③Application ClassLoader
这三个类加载器彼此之间存在一个父子关系,即Application ClassLoader是子、Extension ClassLoader是父、BootStrap ClassLoader是爷,这个父子关系不是继承,而是这几个类加载器里都有一个parent这样的属性,这个parent属性指向一个父“类加载器”。

类加载的第一个步骤“加载”过程中找.class文件的过程:
①给定一个类的全限定类名,比如java.lang.String。

②以Application ClassLoader作为入口根据全限定类名开始执行查找对应的.class文件的逻辑。

③Application ClassLoader不会立即扫描自己负责的目录(Application ClassLoader复责的目录是当前项目对应的目录和第三方库对应的目录),而是把查找的任务交给他的父亲Extension ClassLoader。

④Extension ClassLoader也不会立即扫描自己负责的目录(Extension ClassLoader负责的目录是JDK中的一些扩展库对应的目录(JDK厂商会在标准之外做一些扩展)),而是把查找的任务交给它的父亲BootStrap ClassLoader。

⑤BootStrap ClassLoader也不会立即扫描自己负责的目录(BootStrap ClassLoader负责的是标准库对应的目录),而是把查找的任务交给它的父亲,结果发现没有父亲,因此BootStrap ClassLoader只能扫描自己负责的目录,如果类是标准库中的类,那么在BootStrap ClassLoader这个类加载器中就能找到对应的.class文件,此时查找.class文件的过程就结束了。
如果类不是标准库中的类,则查找.class文件的任务就会交给孩子Extension ClassLoader去执行。

⑥Extension ClassLoader就会扫描自己负责的目录,如果找到对应的.class文件,则查找结束,就执行后续的类加载操作;如果没找到,则把任务交给孩子Application ClassLoader执行。

⑦Application ClassLoader就会扫描自己负责的目录,如果找到对应的.class文件,则查找结束,就执行后续的类加载操作;如果没找到,就会抛出ClassNotFoundException。

双亲委派模型的目的是为了维护类被加载的优先级。

二、JVM垃圾回收机制

Java中new一个对象,就是一次“动态内存申请”。
动态表示运行时(程序运行起来才能确定内存大小),静态表示编译时(编译时就能确定内存大小)。
编译时:int a[5],a数组占据多少内存,在编译过程中就能确定下来,一个int是4字节,5个int就是20字节。

在C语言中使用malloc申请的内存在使用完之后需要通过free来释放,在C++中使用new申请的内存需要通过delete来释放。

Java给出了垃圾回收机制(GC),让JVM自动把不再使用的内存回收掉。而不用手动回收内存,大大降低了程序员的心智负担。

局部变量的生命周期是跟随栈帧的生命周期走的,方法执行结束栈帧销毁,局部变量所对应的内存也就释放了。
静态成员变量的生命周期是整个程序的生命周期,是类对象中的一部分,类加载之后是不会卸载的,所以静态成员变量无需释放。
所以GC回收的是堆上的对象。

GC分为两个步骤:

2.1找到垃圾

有两种主流方案:

2.1.1引用计数(比如Python,PHP中用到)

new出来的对象单独安排一块空间来保存一个计数器,这个计数器用来进行引用计数,这个计数器描述了这个对象有几个引用在指向它。
比如:
{
Test t = new Test();
Test t2 = t;
}
出了{}之后,t和t2就被销毁了,引用计数就归0了。当对象的引用计数为0时,此时这个对象就可以视为垃圾了。

但Java没有使用引用计数,因为引用计数有两个缺陷:
①比较浪费内存。因为每个new出来的对象都要单独安排一个计数器来保存它的引用计数,计数器至少要占据两个字节的内存空间,如果对象很少或者对象很大这时影响不大;如果对象很小并且很多这时计数器占据的空间就不容忽视了,内存就被浪费了很多。
②循环问题。
比如:
class A {
public A t;
}
class Test {
public static void main(String[] args) {
A a = new A();
A b = new A();
a.t = b;
b.t = a;
a = null;
b = null;
}
}
在这里插入图片描述
此时a和b两个引用已经被销毁了,new出来的两个对象已经无法被其它代码访问到,但是它们的引用计数不为0,这时这两个对象是不能回收的,第一个对象引用了第二个对象,第二个对象引用了第一个对象。要想拿到第一个对象就要先拿到第二个对象,要想拿到第二个对象就要先拿到第一个对象,这构成了逻辑上的循环错误。

2.1.2可达性分析(比如Java中用到)

可达性分析本质上是时间换空间。有一个/一组线程周期性地扫描代码中的所有对象,从一些特定的对象出发,尽可能地进行遍历访问(比如类似于N叉树遍历),把所有能够被访问到的对象都标记成“可达”,不能被访问到的未被标记的对象就是垃圾了。
可达性分析开始遍历访问的起点对象有很多,比如:局部变量中引用的对象、常量池中引用的对象、方法区中类静态属性引用的对象……,这些起点对象统称为GCRoots。
可达性分析是周期性进行的,因为某个对象是否是垃圾是会随着代码的执行而发生改变的(比如这个对象现在不是垃圾,代码执行了一段时间之后就变成垃圾了)。所以可达性分析比较消耗系统资源,导致系统时间开销较大,相比之下引用计数通过计数器来衡量当前对象是否是垃圾,比较精准,时间开销比较小。

2.2释放垃圾

有三种基本思路:

2.2.1标记清除

把垃圾对象直接释放掉,但这个方案非常不好,因为这会产生很多的内存碎片。我们释放内存是为了让其它代码能够申请内存,而申请内存时我们申请到的都是连续的内存空间。如果使用标记清除使用了一段时间,那么内存中出现内存碎片的情况将会非常严重,导致内存申请变得十分困难。

2.2.2复制算法

把内存分成两份,一次只用其中的一半。通过复制的方式把有效的对象归类到另一半,再统一释放原来那一半的所有空间。
复制算法可以有效解决内存碎片问题,但这个方案也有缺点:
(a)内存要浪费一般,内存利用率低。
(b)如果有效的对象非常多,那么拷贝的开销就会很大。

2.2.3标记整理

这个方法既能够解决内存碎片的问题,又能够解决复制算法中内存利用率低的问题,但拷贝的开销和复制算法差不多。
标记整理类似于顺序表删除元素时的搬运操作。在内存空间中把有效的对象一个一个地往内存空间的前面搬运,然后把内存空间后面的空间回收掉。
在这里插入图片描述

2.2.4分代回收

JVM释放内存的方法,是上述三种基本思路的结合体,即分代回收。
把堆分成两部分,这两部分不是等分的。左边称为新生代,右边称为老年代。新生代中有一个幸存区和一个伊甸区,幸存区里等分为两部分。
在这里插入图片描述
①刚new出来的新的对象放在伊甸区,从对象诞生到可达性分析扫描开始,这个过程虽然时间不长(往往是毫秒~秒级别),但在这个时间里大部分对象都会成为垃圾,即大部分对象都活不过一轮GC。

②伊甸区中经过一轮GC后仍然可达的对象,就会通过复制算法被拷贝到幸存区。然后释放整个伊甸区的内存。由于伊甸区中幸存下来的可达对象并不多,复制开销不大,所以这里非常适合用复制算法。

③GC扫描线程也会扫描幸存区,然后把GC扫描到的可达对象通过复制算法拷贝到幸存区的另一半,然后释放掉幸存区原来那一半的内存。对于幸存区之间的拷贝,每一轮GC会拷贝多个对象、也会淘汰多个对象。

④当某个对象在幸存区中存活过很多轮GC扫描之后,JVM就认为这个对象在短时间内应该是不会成为垃圾的,就会把这个对象拷贝到老年代。

⑤进入老年代的对象也会被GC扫描,但老年代GC扫描的频率会比新生代GC扫描的频率低很多(这减少了GC扫描的开销)。老年代使用标记整理的方式对内存进行回收。

新生代使用复制算法进行垃圾回收,老年代使用标记整理进行垃圾回收。

分代回收是JVM中主要的垃圾回收思想方法。但是在垃圾回收器具体实现的时候,可能还会有一些调整和优化。

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

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

相关文章

Nginx配置文件中的关键字是什么?详细解释来了

点击上方蓝字关注我 Nginx 是一款高性能的 Web 服务器软件,同时也是一款反向代理服务器软件。Nginx 的配置文件通常是 /etc/nginx/nginx.conf,以下是一个典型的配置文件,并对其中的关键字进行详细解释。 1. 配置文件 perlCopy codeuser ngin…

计算机编程零基础编程学什么语言,中文编程工具构件简介软件下载

计算机编程零基础编程学什么语言,中文编程工具构件简介软件下载 给大家分享一款中文编程工具,零基础轻松学编程,不需英语基础,编程工具可下载。 这款工具不但可以连接部分硬件,而且可以开发大型的软件,象如…

Redis集群(新)

1.什么是集群 Redis集群实现了对Redis的水平扩容,可实现并发写操作,启动n个redis节点,将数据分别存储在不同的节点中,每块节点负责不同区域的插槽,所以Redis集群通过分区来提供一定程度的可用性。 Redis集群现采用的是…

EFAK-v3.0.1版部署与使用

一、前言 EFAK((Eagle For Apache Kafka,以前称为Kafka Eagle)用于在使用 Topic 的情况下监控 Kafka 集群。包含Offset 的产生、Lag的变化、Partition的分布、Owner、Topic的创建以及修改的时间等信息。 二、环境&安装包 官方下载连接E…

Spring Boot 整合MyBatis-Plus 详解

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形…

积分球吸收光谱测量的领域有哪些?

积分球吸收光谱测量是一种常用的吸收光谱测量方法,它通过将样品放置在积分球的入口处,球内的光线经过多次反射后形成均匀的照度分布,然后使用光度计或光谱仪对光线进行测量,可以获得样品的相关参数。 在积分球吸收光谱测量中&…

十大排序之选择排序(详解)

文章目录 🐒个人主页🏅算法思维框架📖前言: 🎀选择排序 时间复杂度O(n^2)🎇1. 算法步骤思想🎇2.动画实现🎇 3.代码实现 🐒个人主页 🏅算法思维框架 &#x1f…

Java数组的复制、截取(内含例题:力扣-189.轮转数组)

目录 数组的复制、截取: 1、使用Arrays中的copyOf方法完成数组的拷贝 2、使用Arrays中的copyofRange方法完成数组的拷贝 题目链接: 数组的复制、截取: 1、使用Arrays中的copyOf方法完成数组的拷贝 public class Csdn {public static vo…

Edit And Resend测试接口工具(浏览器上的Postman)

优点 可以不用设置Cookie或者Token,只设置参数进行重发接口测试API 使用Microsoft Rdge浏览器 F12——然后点击网络——在页面点击发起请求——然后选择要重发的请求右键选择Edit And Resend——在网络控制台设置自己要设置的参数去测试自己写的功能

GEE:通过将 Landsat 5、7、8、9 的 C02 数据集合并起来,构建 NDVI 长时间序列

作者:CSDN @ _养乐多_ 本文记录了在 Google Earth Engine(GEE)平台上,将 Landsat-5、Landsat-7、Landsat-8 和 Landsat-9 的数据合成为一个影像集合,并生成 NDVI(归一化植被指数)的时间序列的代码。 代码封装成了函数,方便调用,结果如下图所示, 在实际应用中,可能…

基于光纤环形激光器的optisystem仿真及其传感应用

近年来,光纤传感器在航空航天领域,工业制造,医疗等领域引起了越来越多的关注,因为他们体积小,结构简单,灵敏度高,抗电磁干扰强,防腐性能好的特点。各种各样的传感器结构被设计出来&a…

哨兵1号回波数据(L0级)包格式解析与成像参数提取

坑爹的格式,具体有多坑往下看就知道了。matlab代码在文末。 先上首字母缩写: 再来回波数据包的格式图 1. 数据包格式 众所周知,解包的第一步是找帧头和帧长,找到第4~5字节,帧长码为“0x3761”,转十进制为14777,然而实际第一帧整帧的长度是14184。。。你要是加6我还能…

如何打造垂直LLM的护城河

B2B人工智能初创企业的一个伟大策略是打造“垂直人工智能”产品:成为特定行业的人工智能助手,比如律师、金融服务、医生。 听起来很简单:你可以利用LLM的超能力,并将其应用于宠物行业的特定数据和用例。 这就是我们在Explain所做的…

Leetcode—94.二叉树的中序遍历【简单】

2023每日刷题(四十) Leetcode—94.二叉树的中序遍历 C语言实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array mus…

【matlab版本的ggplot2】

gramm (complete data visualization toolbox, ggplot2/R-like) 来源:Morel, Pierre. “Gramm: Grammar of Graphics Plotting in Matlab.” The Journal of Open Source Software, vol. 3, no. 23, The Open Journal, Mar. 2018, p. 568, doi:10.21105/joss.00568…

【开源】基于JAVA的森林火灾预警系统

项目编号: S 019 ,文末获取源码。 \color{red}{项目编号:S019,文末获取源码。} 项目编号:S019,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 系统基础模块2.3 烟…

【Linux】匿名管道与命名管道,进程池的简易实现

文章目录 前言一、匿名管道1.管道原理2.管道的四种情况3.管道的特点 二、命名管道1. 特点2.创建命名管道1.在命令行上2.在程序中 3.一个程序执行打开管道并不会真正打卡 三、进程池简易实现1.makefile2.Task.hpp3.ProcessPool.cpp 前言 一、匿名管道 #include <unistd.h&g…

vivado产生报告阅读分析22

“ Advanced ”选项卡 “ Advanced ” &#xff08; 高级 &#xff09; 选项卡如下图所示。 在“ Advanced ”选项卡中提供了以下字段 &#xff1a; • “ Report ” &#xff08; 报告 &#xff09;&#xff1a; 选中“ Advanced ”选项卡中的“ Cells to Analyze ” &…

Vatee万腾的科技探险:vatee数字化力量的前瞻征途

在Vatee万腾的科技探险中&#xff0c;我们领略到了一场数字化力量的前瞻征途&#xff0c;这是一次引领未来的创新之旅。Vatee万腾以其独特的科技理念和数字化力量&#xff0c;开启了一次引领行业的前瞻性征途&#xff0c;为数字化未来描绘出了崭新的篇章。 Vatee万腾的数字化力…

推荐6款本周 yyds 的开源项目

&#x1f525;&#x1f525;&#x1f525;本周GitHub项目圈选: 主要包含 链接管理、视频总结、有道音色情感合成、中文文本格式校正、GPT爬虫、深度学习推理 等热点项目。 1、Dub 一个开源的链接管理工具&#xff0c;可自定义域名将繁杂的长链接生成短链接&#xff0c;便于保…