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中主要的垃圾回收思想方法。但是在垃圾回收器具体实现的时候,可能还会有一些调整和优化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RAM模型从数据准备到pretrain、finetune与推理全过程详细说明

提示:RAM模型:环境安装、数据准备与说明、模型推理、模型finetune、模型pretrain等 文章目录 前言一、环境安装二、数据准备与解读1.数据下载2.数据标签内容解读3.标签map内容解读 三、finetune训练1.微调训练命令2.load载入参数问题3.权重载入4.数据加载…

大数据技术之数据安全与网络安全——CMS靶场实训

大数据技术之数据安全与网络安全——CMS靶场实训 在当今数字化时代,大数据技术的迅猛发展带来了前所未有的数据增长,同时也催生了对数据安全和网络安全的更为迫切的需求。本篇博客将聚焦于大数据技术背景下的数据安全与网络安全,并通过CMS&a…

4.操作系统常见面试题(2)

3.4 虚拟内存 直接使⽤物理内存会产⽣⼀些问题 1. 内存空间利⽤率的问题:各个进程对内存的使⽤会导致内存碎⽚化,当要⽤ malloc 分配⼀块很⼤的内存空间时,可能会出现虽然有⾜够多的空闲物理内存,却没有⾜够⼤的连续空闲内存这种…

点大商城V2.5.3分包小程序端+小程序上传提示限制分包制作教程

这几天很多播播资源会员反馈点大商城V2.5.3小程序端上传时提示大小超限,官方默认单个包都不能超过2M,总分包不能超20M。如下图提示超了93KB,如果出现超的不多情况下可采用手动删除一些images目录下不使用的图片,只要删除超过100KB…

82基于matlab GUI的图像处理

基于matlab GUI的图像处理,功能包括图像一般处理(灰度图像、二值图);图像几何变换(旋转可输入旋转角度、平移、镜像)、图像边缘检测(拉普拉斯算子、sobel算子、wallis算子、roberts算子&#xf…

unordered_map 与 unordered_set 的模拟实现

unordered_map 与 unordred_set 的模拟实现与 map 与 set 的模拟实现差不多。map 与 set 的模拟实现中,底层的数据结构是红黑树。unordered_map 与 unordered_set 的底层数据结构是哈希表。因此,在模拟实现 unordered_map 与 unordred_set 之前你必须确保…

nodejs微信小程序+python+PHP-青云商场管理系统的设计与实现-安卓-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…

org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder

密码,加密,解密 spring-security-crypto-5.7.3.jar /** Copyright 2002-2011 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with t…

HTML新特性【缩放图像、图像切片、平移、旋转、缩放、变形、裁切路径、时钟、运动的小球】(二)-全面详解(学习总结---从入门到深化)

目录 绘制图像_缩放图像 绘制图像_图像切片 Canvas状态的保存和恢复 图形变形_平移 图形变形_旋转 图形变形_缩放 图形变形_变形 裁切路径 动画_时钟 动画_运动的小球 引入外部SVG 绘制图像_缩放图像 ctx.drawImage(img, x, y, width, height) img &#xf…

开源与闭源

我的观点: 开源与闭源软件都有各自的优势和劣势,没有绝对的对错之分。.. 一、开源和闭源的优劣势比较 开源的好处与劣处 优势: 创新与合作:开源软件能够吸引更多的开发者参与到项目中来,促进创新和合作。开放的源代码…

【网易云商】构建高效 SaaS 系统的技术要点与最佳实践

SaaS 是什么 定义 相信大家都对云服务中的 IaaS、PaaS、SaaS 早就有所耳闻,现在更是衍生出了 aPaaS、iPaaS、DaaS 等等的类似概念。对于 SaaS 也有各种各样的定义,本文给出的定义是: SaaS 是一种基于互联网提供服务和软件的交付模式&#xf…

一文彻底看懂Python切片,Python切片理解与操作

1.什么是切片 切片是Python中一种用于操作序列类型(如列表、字符串和元组)的方法。它通过指定起始索引和结束索引来截取出序列的一部分,形成一个新的序列。切片是访问特定范围内的元素,就是一个Area。 说个笑话:切片不是切片,而是切片,但是又是切片。大家理解下呢(末…

80C51单片机----数据传送类指令

目录 一.一般传送指令,即mov指令 1.16位传送(仅1条) 2.8位传送 (1)目的字节为A(累加器) (2)目的字节为Rn(工作寄存器) (3)目的字节为direct…

java中的String.format()方法详解

介绍 String.format() 是 Java 中的一个字符串格式化方法,它用于生成指定格式的字符串。这个方法可以接受一个或多个参数,并将它们按照指定的格式插入到字符串中。它使用了类似于 C 语言中的 printf 函数的语法。 String.format() 方法的使用格式如下&…

Tars框架 Tars-Go 学习

Tars 框架安装 网上安装教程比较多,官方可以参数这个 TARS官方文档 (tarsyun.com) 本文主要介绍部署应用。 安装完成后Tars 界面 增加应用amc 部署申请 amc.GoTestServer.GoTestObj 名称不知道的可以参考自己创建的app config 点击刷新可以看到自己部署的应用 服…

微机原理_3

一、单项选择题(本大题共15小题,每小题3分,共45分。在每小题给出的四个备选项中,选出一个正确的答案,请将选定的答案填涂在答题纸的相应位置上。) 在 8086 微机系统中,完成对指令译码操作功能的部件是()。 A. EU B. BIU C. SRAM D. DRAM 使计算机执行某…

【Rust日报】2023-11-22 Floneum -- 基于 Rust 的一款用于 AI 工作流程的图形编辑器

Floneum -- 基于 Rust 的一款用于 AI 工作流程的图形编辑器 Floneum 是一款用于 AI 工作流程的图形编辑器,专注于社区制作的插件、本地 AI 和安全性。 Floneum 有哪些特性: 可视化界面:您无需任何编程知识即可使用Floneum。可视化图形编辑器可…

2023年金融信创行业研究报告

第一章 行业概况 1.1 定义 金融信创是指在金融行业中应用的信息技术,特别是那些涉及到金融IT基础设施、基础软件、应用软件和信息安全等方面的技术和产品。这一概念源于更广泛的“信创 (信息技术应用创新)”,即通过中国国产信息技术替换海外信息技术&a…

某60区块链安全之未初始化的存储指针实战二学习记录

系列文章目录 文章目录 系列文章目录未初始化的存储指针实战二实验目的实验环境实验工具实验原理实验内容实验过程EXP利用 未初始化的存储指针实战二 实验目的 学会使用python3的web3模块 学会分析以太坊智能合约未初始化的存储指针漏洞 找到合约漏洞进行分析并形成利用 实验…

【Vue】图片切换

上一篇&#xff1a; vue的指令 https://blog.csdn.net/m0_67930426/article/details/134599378?spm1001.2014.3001.5502 本篇所需要的指令有&#xff1a; v-on v-bind v-show <!DOCTYPE html> <html lang"en"> <head><meta charset"…