javaEE-8.JVM(八股文系列)

news2025/2/6 11:32:43

目录

一.简介

二.JVM中的内存划分

JVM的内存划分图:

堆区:​编辑

栈区:​编辑

程序计数器:​编辑

元数据区:​编辑

经典笔试题:

三,JVM的类加载机制

1.加载:

2.验证:

3.准备:

4.解析:

5.初始化:

双亲委派模型

概念:

 JVM的类加载器 默认有三种:

双亲委派模型的工作流程:

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

 垃圾回收步骤:

1.识别出垃圾

1)引用计数

2)可达性分析

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

1)标记-清除:

2)复制算法

3)标记整理

分带回收


一.简介

JVM : java Virtual Machine 的简称,意为Java虚拟机。

虚拟机是指通过软件模拟的具有完整硬件功能的、运⾏在⼀个完全隔离的环境中的完整计算机系统。常⻅的虚拟机:JVM、VMwave、Virtual Box。

JVM 和其他两个虚拟机的区别:

1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;

2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进⾏了裁剪。

JVM 是⼀台被定制过的现实当中不存在的计算机。

java的执行流程是:先通过javac 将.java文件转为.class(字节码文件)文件,之后在某个平台执行;然后 通过JVM 将.class文件转换为CPU能识别的机器指令。

因此,编写一个java程序,只需要发布.class文件就行了。JVM拿到.class文件,就知道该如何转换了.

二.JVM中的内存划分

JVM也相当于一个进程,在启动一个java程序后,需要个JVM分配资源空间.

JVM从系统中申请的内存,会根据java程序中不同的使用途径,为其分配空间.这就是内存划分.

JVM会将申请到的空间划分成几个区域,每个区域有不同的功能,

JVM的内存划分图:

堆区:

存放的是代码中new出来的对象,对象中的非静态成员也在堆区.

栈区:

包含了一些方法调用关系和局部变量.

由本地方法栈和虚拟机栈组成,本地方法栈是JVM内部,是由C++写的;虚拟机栈保存了一些java的方法调用和局部变量。

平时所说的栈区,指的是虚拟机栈,

程序计数器

这个区域比较小,专门用来保存下一条要执行的java指令的地址。

元数据区:

包含了一些辅助性质的,描述性质的属性。元数据区也叫做方法区。

元数据是计算机中的一个常见术语(Meta data)。

对于硬盘来说,不仅要存储文件的数据本体,还要存储一些辅助信息,像文件的大小,文件的位置,文件的使用权限,文件的拥有者....这些都称为“元数据”。

一个程序中,有哪些类,有哪些方法,每个方法中有哪些指令,....这些信息都会保存在JVM的元数据区.

对于堆区和元数据区,整个进程中只有一份;而对于栈区和程序计数区,在内存中是有很多份的.

经典笔试题:

class Test {
    private int n;
    private static int m;
}
public static void main(String args[]){
    Test t = new Test();
}
   

问: n,m,t 都在哪块JVM的哪个内存区域中?

n属于局部变量,在作用域中生效,出作用域就销毁了,存在栈区.

m:属于静态变量,存在元数据区。

t:是new出来了一个Test对象,t中保存的是Test的地址,属于局部变量,保存在栈区;而Test对象则保存在堆区.

区分变量在内存的哪个区域上,最重要的就是确定该变量的"形态",是 局部变量/成员变量/静态变量....

三,JVM的类加载机制

类加载指的是JVM把.class文件从硬盘读取到内存,进行一系列的校验解析的过程.转换成类对象的过程.

类加载过程大致分为五步:

1.加载:

把.class文件找到并打开,读取到文件中的内容.

2.验证:

需要确定当前读到的文件是合法的.class文件(字节码文件).否则若读到错误的文件,后面的工作就白费了.

具体的验证依据是在java的虚拟机规范中,有明确的格式说明:

左面这一列是类型,右面这一列是名字.

:

也叫做:magic number 魔幻数字,用来标识二进制文件中的格式的类型.

:

这两个都是版本号,u4 是主版本,u2 是次版本.属于JVM内部的版本,JVM会验证.class文件的版本号是否符合要求.

一般来说 高版本的JVM可以运行低版本的.class文件,反之不行.

3.准备:

为类对象申请内存空间.此时申请到的内存空间都为默认值 为全0的.

4.解析:

主要是针对类中的字符串常量进行处理.

将常量池中的 符号引用 替换为 直接引用 的过程,也就是初始化常量的过程.

我们知道,在.class文件中,是不存在地址的,而对于创建的字符串常量,变量中保存的是常量的地址,这又是怎样记录的呢?

class Test{
    private String s="hello";
}

这个hello在.class文件中,是否会保存呢?

当然是要保存的,只不过s中保存的是一个字符串常亮的"偏移量".

在文件中,不存在地址这样的概念,地址是内存的地址,而文件是在硬盘中的.

为了保存字符串常来那个,可以存储一个"偏移量"的概念, 这里的偏移量就认为是符号引用.

之后,把.class文件加载到内存中,就有地址了,s中的值就能根据偏移量来转换为真正地址了,也就是直接引用.

5.初始化:

针对类对象,完成后续的初始化操作.

执行静态代码块,构造方法,还可能触发父类加载.....

双亲委派模型

在类加载过程的第一步:加载环节中使用 双亲委派模型 描述如何查找.class文件的策略.

JVM在进行类加载的时候,有一个专门的模块,称为"类加载器".(ClassLoader)

概念:

双亲委派模型: 如果一个类加载器收到一个类加载的请求,他首先不会自己加载该类,而是将这个类委派给父类加载器,让父类加载器去完成对类的加载.每层次的类加载器都是这样委派,最终所有的加载请求都会到达 最顶层的类加载器,直到当父类加载器反馈自己无法完成这个类加载请求时,子类加载器就会尝试自己完成加载.

 JVM的类加载器 默认有三种:

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

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

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

 这三个类加载器存在父子类(二叉树关系)关系.

ApplicationClassLoader的父类是ExtensionClassLoader;

ExtensionClassLoader的父类是BootstrapClassLoader,BootstrapClassLoader属于顶层父类。

双亲委派模型的工作流程:

1.类加载任务先从ApplicationClassLoader为入口,开始工作;

2.ApplicationClassLoader自己不会立即搜索自己负责的目录,会将搜索的任务向上传递给父类;

3.代码进入ExtensionClassLoader的范畴,同样,ExtensionClassLoader 也不是立即搜索自己负责的目录,继续将搜索的任务向父类传递;

4.代码进入BootstrapClassLoader的范畴,由于BootstrapClassLoader是顶级父类了,就会真正进行负责搜索目录(标准库目录),尝试在标准库目录中找到符合要求的.Class文件;

5.若是找到了,就会进入打开文件,读文件流程了,此时类加载步骤就结束了;若是没有找到,就会返回到子类的类加载器中,继续尝试加载。

6.若是在ExtensionClassLoader类加载器中找到符合要求的.Class文件,此时类加载步骤就结束了;若还未找到,就会返回给子类加载器ApplicationClassLoader继续尝试加载.

7.若在ApplicationClassLoader类加载器中搜索到了,此时类加载就结束了,就会进入后续流程;若是没有找到,就会继续向子类寻找,由于ApplicationClassLoader是底层了,就表示类加载失败了.

这一系列的列加载机制,目的是为了保证这几个类加载器的优先级顺序.

这个类加载器是系统默认的类加载机制,也可以自己实现类加载机制,可以与默认机制不同.

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

垃圾回收指的是让程序自动回收内存,JVM中的内存分为好几种,要回收的是堆区的内存;

元数据区和程序计数区的内存不需要回收,栈区中存放的都是局部变量申请的内存,在代码结束后,会自动销毁(属于栈区自己的特点,和垃圾回收没有关系)。

回收内存其实就是回收对象,垃圾回收时,将堆区上的若干个对象释放掉。

堆区内存根据垃圾回收,又分为三类区间:

 垃圾回收步骤:

1.识别出垃圾

要判定哪些对象是垃圾,哪些对象不是垃圾。就是判断该对象是否还需要使用。

在java中,使用对象,一定是通过引用指向使用对象的方式使用,若该对象没有引用指向,则表示该对象不再被使用,就可以进行垃圾回收了。

class Test{
....
}
void func(){
Test t = new Test(); 

}

这个代码中,执行结束后,t属于局部变量,存在于栈区,会被直接释放掉,Test对象在执行完后,由于没有对象指向了,也就属于垃圾了,就会被垃圾回收。

对于一些更复杂的代码,判定过程也就更加复杂。

Test t1 = new Test();
Test t2 = t1;
Test t3 = t2;
Test t4=t3;
....

很多引用都指向了同一个对象Test,只有当所用的引用都结束了,才能释放Test对象,但每个引用的生命周期又不一样,就很难判断了。

于是又设计一些方法来记录对象的引用:

1)引用计数

给每个对象再分配一个额外的空间,保存当前对象引用个数,当有一个引用指向了该对象,引用计数就+1,一个引用结束后,引用计数就-1.

此时的垃圾回收机制就是:有一个专门的扫描线程,取获取每个对象的引用计数的情况,当引用计数为0时,就表示该对象没有引用指向了,不再使用了,也就可以释放了。

class Test {
    ....
}
void func() {
Test t1 = new Test();
Test t2 = t1;
}

这个代码的内存分配:

引用计数 存在的问题:

1)耗费额外的空间

引用计数需要耗费一个额外的空间,若对象本身占用的内存就比较小,总的对象数目有很多,那么总的消耗空间就会非常多。

2)可能出现“循环引用问题”:

class Test{
    Test t;
}
Test t1 = new Test();
Test t2 = new Test();
t1.t = t2;
t2.t = t1;
t1 = null;
t2 = null;

当t1和t2还未被置为null的时候,此时的内存是这样的情况:

 当t1和t2都被置为null后,t1,t2内存被释放,但Test对象中的t还未被释放:

此时,Test是的引用计数还都不0,不能被GC回收,但又无法使用,就产生了循环引用问题,这种情况下的引用计数就无法被正常使用了。

引用计数 这种思想 并未在java中使用,在别的语言的垃圾回收机制中有使用到。

2)可达性分析

(JVM的垃圾回收机制 识别垃圾 采用的是这种思想)

可达性分析本质上是采用“时间”换“空间”的方法。

相较于 引用计数,可达性分析要消耗更多的时间去“遍历”,不会存在上面 引用计数 中的问题。

可达性分析:一个java代码中,会定义很多变量,从这些变量为起点,向下“遍历”:从这些变量中持有的引用类型的成员,再向下遍历,所有能被访问到的对象,一定不是垃圾了,而未被访问到的对象,就是垃圾了,要被就行回收。

JVM自身有扫描线程,会不停地扫描代码,看是否有对象无法被遍历到;JVM本身是知道一共有多少个对象的。

class Node{
    char root;
    Node left;
    Node right;
}
Node BuildNode{
Node a = new Node();
Node b = new Node();
Node c = new Node();
Node d = new Node();
Node e = new Node();
Node f = new Node();
Node g = new Node();
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.right = f;
e.left = g;
}
public static void main(String args[]){
Node root = BuildNode();
}

代码中的树是这个样子,

在这个代码中,虽然只有一个root这样的引用,但实际上有7个对象都是可达的,

若代码中出现: c.right=null;此时f就是不可达的,f就属于垃圾了.要进行回收.

若a=null;那么整个二叉树都是不可达的了.都要进行垃圾回收.

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

具体的释放方法有三种.

1)标记-清除:

把标记为垃圾的对象,直接进行释放。(最直接的方法)

这种做法可能会产生大量的“内存碎片”,会存在很多小的,离散的可用空间。

可能导致后续申请内存空间失败,申请内存空间都是一次申请一个连续的内存空间,此时可能内存中总得空间是够当前要申请的内存空间的,但内有连续的内存空间够分配,就可能申请失败。

2)复制算法

先把申请的内存分成两部分,对象申请内存时都在一半的内存中创建;进行释放时,把不是垃圾的对象的内存复制到另一半内存中,然后把带有垃圾的半个内存全部释放掉。

将不是垃圾的对象都复制到另半个内存中:

再把左半部分的内存中的对象都释放掉

这个方法也存在一些问题:

1、每次释放内存,要释放一半的内存,总的可用内存减少了很多。

2、若引用的对象很多,对对象的复制也要消费很大的开销。

3)标记整理

类似于顺序表中,删除元素的方法。

遍历整个内存,若有遍历到的对象标记为垃圾,不用管,后面遍历到不是垃圾的对象内存就覆盖垃圾的内存空间,这样既不会存在“内存碎片”,又不会一次释放很多的内存。

这个方法的缺点是搬运内存会有很大的开销。

上面的方法都有一定的缺点和问题,因此,JVM并没有直接使用上面的方法,而是对上面的方法思想,采用了一个·“综合性”方案:“分带回收”。

分带回收

依据不同种类的对象,采用不同的回收方式。

JVM引入了一个概念:年龄。

JVM的扫描线程会不断的扫描内存,若该对象是可达的,年龄就+1;

JVM根据对象年龄的不同,将内存分为两个区域:新生代 和 老年代。

新生代中又划分了三个大小不等的区域:其中一个大的区域叫伊甸区 和 两个小的等大的生存区(幸存区)。

回收过程:

1.当创建出一个对象后,该对象会先被创建到伊甸区,(伊甸区的对象大多都被第一轮GC扫描到了,就会被回收掉)

2.第一轮GC后,少数存活的对象通过复制算法被送到其中一个生存区,扫描还在继续,生存区中被标记的对象就会被清除掉,极少数的生存区的对象会再次通过复制算法,从一个生存区复制到另一个生存区,这样循环扫描复制,每经过一轮GC的扫描,年龄就会+1.

3.当这个对象在生存区经过了若干轮扫描,年龄已经很大了,说明这个对象的生命周期可能很长,就将这个对象拷贝到老年代,老年代中的对象经过GC扫描的频率要比新生代低很多。

4.当扫描老年代中的对象,也被标记为垃圾了,也会进行释放。

这个分带回收就类似于找工作一样:

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

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

相关文章

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化:知识蒸馏的范式迁移与工程实践 🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 摘要 在大型语言模型&#xff…

数据结构:时间复杂度

文章目录 为什么需要时间复杂度分析?一、大O表示法:复杂度的语言1.1 什么是大O?1.2 常见复杂度速查表 二、实战分析:解剖C语言代码2.1 循环结构的三重境界单层循环:线性时间双重循环:平方时间动态边界循环&…

[创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁

目录 前言: 从燃油汽车到智能汽车:工业革命下的价值变迁 前言: 燃油汽车,第一次、第二次工业革命,机械化、电气化时代的产物,以机械和电气自动化为核心价值。 智能汽车,第三次、第四次工业革…

vue页面和 iframe多页面无刷新方案和并行 并接入 micro 微前端部分思路

前: 新进了一家公司,公司是做电商平台的, 用的系统竟然还是jsp的网站,每次修改页面还需要我下载idea代码,作为一个前端, 这可不能忍,于是向上申请,意思你们后台做的太辣鸡,我要重做,经领导层商议从去年6月开始到今年12月把系统给重构了 公司系统采用的是每个jsp页面都是一个ifr…

Python 自学秘籍:开启编程之旅,人生苦短,我用python。

从2009年,用了几次python后就放弃了,一直用的php,现在人工智能时代,完全没php什么事情。必须搞python了,虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代&#xff…

每日一题洛谷P5721 【深基4.例6】数字直角三角形c++

#include<iostream> using namespace std; int main() {int n;cin >> n;int t 1;for (int i 0; i < n; i) {for (int j 0; j < n - i; j) {printf("%02d",t);t;}cout << endl;}return 0; }

解决DeepSeek服务器繁忙问题:本地部署与优化方案

deepseek服务器崩了&#xff0c;手把手教你如何在手机端部署一个VIP通道&#xff01; 引言 随着人工智能技术的快速发展&#xff0c;DeepSeek等大语言模型的应用越来越广泛。然而&#xff0c;许多用户在使用过程中遇到了服务器繁忙、响应缓慢等问题。本文将探讨如何通过本地部…

【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)

【后端开发】系统设计101——通信协议&#xff0c;数据库与缓存&#xff0c;架构模式&#xff0c;微服务架构&#xff0c;支付系统&#xff08;36张图&#xff09; 文章目录 1、通信协议通信协议REST API 对比 GraphQL&#xff08;前端-web服务&#xff09;grpc如何工作&#x…

Java基础——分层解耦——IOC和DI入门

目录 三层架构 Controller Service Dao ​编辑 调用过程 面向接口编程 分层解耦 耦合 内聚 软件设计原则 控制反转 依赖注入 Bean对象 如何将类产生的对象交给IOC容器管理&#xff1f; 容器怎样才能提供依赖的bean对象呢&#xff1f; 三层架构 Controller 控制…

武汉火影数字|VR虚拟现实:内容制作与互动科技的奇妙碰撞

VR虚拟现实是一种利用计算机技术生产三维虚拟世界的技术&#xff0c;通过头戴式显示器、手柄等设备&#xff0c;用户可以身临其境地感受虚拟世界&#xff0c;与其中的物体进行自然交互。 当内容制作遇上 VR&#xff0c;会发生什么&#xff1f; 当内容制作遇上VR&#xff0c;就像…

SpringBoot扩展篇:@Scope和@Lazy源码解析

SpringBoot扩展篇&#xff1a;Scope和Lazy源码解析 1. 研究主题及Demo2. 注册BeanDefinition3. 初始化属性3.1 解决依赖注入3.2 创建代理 ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary3.3 代理拦截处理3.4 单例bean与原型bean创建的区别 4. …

tkvue 入门,像写html一样写tkinter

介绍 没有官网&#xff0c;只有例子 安装 像写vue 一样写tkinter 代码 pip install tkvue作者博客 修改样式 import tkvue import tkinter.ttk as ttktkvue.configure_tk(theme"clam")class RootDialog(tkvue.Component):template """ <Top…

Hackmyvm Connection

基本信息 难度&#xff1a;简单 靶机&#xff1a;192.168.194.11 kali&#xff1a;192.168.194.9 扫描 常规nmap扫描起手 nmap -sT -sV -A -T4 192.168.194.11 -p- 查看smb服务开启目录 139和445端口的smb服务直接以访客账号登录&#xff0c;无需密码验证成功。对应的ht…

Unity 2D实战小游戏开发跳跳鸟 - 记录显示最高分

上一篇文章中我们实现了游戏的开始界面,在开始界面中有一个最高分数的UI,本文将接着实现记录最高分数以及在开始界面中显示最高分数的功能。 添加跳跳鸟死亡事件 要记录最高分,则需要在跳跳鸟死亡时去进行判断当前的分数是否是最高分,如果是最高分则进行记录,如果低于之前…

数据结构(2)——线性表与顺序表实现

目录 前言 一、线性表 二、顺序表 2.1概念 2.2类型的选择 2.3实现 1.初始化 2.检查是否需要扩容 3.尾插 4.尾删 5.头插 6.头删 7.某一个位置添加 8.某一个位置删除 9.基于某一位置的尾插删 10.查找 11.修改 12.销毁 总结 前言 今天对顺序表进行学习&#xf…

【LeetCode】5. 贪心算法:买卖股票时机

太久没更了&#xff0c;抽空学习下。 看一道简单题。 class Solution:def maxProfit(self, prices: List[int]) -> int:cost -1profit 0for i in prices:if cost -1:cost icontinueprofit_ i - costif profit_ > profit:profit profit_if cost > i:cost iret…

FastReport.NET控件篇之交叉表控件

认识交叉表 上面是交叉表的原型&#xff0c;关键的三个单元格。 单元格①&#xff1a;用于扩展行数据&#xff0c;譬如打印学生成绩表时&#xff0c;每个学生一行&#xff0c;那么这个地方就是以学生姓名列进行打印。 单元格②&#xff1a;用于扩展列数据&#xff0c;譬如打印…

互联网医院开发|互联网医院成品|互联网医院系统定制

互联网医院开发设计风格需综合考量多方面因素&#xff0c;以确保其专业性、易用性与高效性。在界面设计上&#xff0c;应遵循简洁直观的原则。避免过于繁杂的布局&#xff0c;确保关键功能模块清晰呈现&#xff0c;方便用户快速定位与操作。色彩搭配要注重视觉舒适度与专业性&a…

17.3.4 颜色矩阵

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.3.4.1 矩阵基本概念 矩阵&#xff08;Matrix&#xff09;是一个按照长方阵列排列的复数或实数集合&#xff0c;类似于数组。 由…

【C++】多态详细讲解

本篇来聊聊C面向对象的第三大特性-多态。 1.多态的概念 多态通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 编译时多态&#xff1a;主要就是我们前⾯讲的函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调⽤不同的函数&#xff0c;通…