当前位置: 首页 > news >正文

JVM面试常考的4个问题详解

JVM

  • 1.JVM内存区域划分
    • 1)程序计数器
    • 2)栈
    • 3)堆
    • 4)方法区
  • 2.类加载
    • 1)Loading环节
    • 2)Linking环节
    • 3)Intializing
  • 3.双亲委派模型
    • 1)加载java.lang.String
    • 2)加载自己写的Test类
  • 4.JVM的垃圾回收机制(GC)
    • 1)垃圾回收的概念
    • 2)回收的过程:
      • 找垃圾/判定垃圾
      • 释放垃圾
    • 3)垃圾收集器

1.JVM内存区域划分

1)程序计数器

内存中的最小区域,保存了下一条要执行的指令地址在哪。程序想要运行,JVM就得把字节码加载器来访到内存中,程序就会一条一条把指令从内存中取出来放到CPU上执行,因此需要记住当前执行到哪一条。操作系统是以线程为单位进行调度执行的,每个线程都得记录自己的执行位置,因此程序计数器每个线程都会有一个。

2)栈

存放局部变量和方法调用信息,方法调用的时候每次调用一个新的方法就都涉及到“入栈”操作、每次执行完了一个方法都涉及到“出站”操作。每个线程都有一个栈。

3)堆

一个进程只有一份,多个线程共用一个堆,因此也是内存中空间最大的区域。new出来的对象存放在堆中,对象的成员变量自然也是在堆中了。因此可以认为局部变量在栈上,成员变量和new的对象在堆上。

4)方法区

方法区中存放的是“类对象”(.java->.class二进制字节码文件),.class会被加载到内存中,也就被JVM构造成了类对象(加载的过程就称为“类加载”)。这里的类对象就是放到了方法区,类对象描述了这个类长什么样(泪的名字是啥,有啥成员,有哪些方法,每个成员叫啥名字是啥类型,每个方法叫啥名字是啥类型),除此之外静态成员成为了“类属性”,普通的成员是“实例属性”。

2.类加载

把.class文件加载到内存中构建成类对象
在这里插入图片描述

1)Loading环节

先找到对应的.class文件,然后打开并读取.class文件,同时初步生成一个类对象。

2)Linking环节

建立实体之间的联系
a.Verification
验证读到的内容是不是和规范中的格式完全匹配,如果发现这里读到的数据格式不符合规范,就会类加载失败并且抛出异常。
b.Preparation
给静态变量分配内存,并设置初始值。
c.Resolution
解析阶段是java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常亮的过程。.class文件中常量是集中放置的,没一个常量都有一个编号,.class稳健的结构体初始化情况只是记录了编号,就需要根据编号找到对应的内容填充到类对象中。

3)Intializing

真正地对类对象初始化,尤其针对静态成员。

经典面试题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

加载:程序是从main方法开始执行的,main是Test的方法,因此要执行main就需要先加载Test。Test继承自B要加载Test就要先加载B,B继承自A就要先加载A。因此先有前两行AB的静态代码块。
构造:要想构造Test,就得先构造B,要想构造B,就得先构造A。对于A来说构造过程(构造代码块->构造方法的执行)

因此大的原则

  • 创建实例前必须进行类加载,类加载阶段会进行静态代码块的执行
  • 静态代码块只是在类加载阶段执行一次
  • 每次实例化,构造代码块先进行然后构造方法
  • 分类执行在子类前

3.双亲委派模型

JVM中的类加载器如何根据类的全限定名(java.lang.String)找到.class文件的过程。

JVM里提供了专门的对象叫做“类加载器”负责进行类加载,当然找文件的过程也是类加载器来负责的,.class文件放置的位置可能有很多,因此JVM里面提供了多个类加载器,每个类加载器负责一个分区,默认的类加载器主要是3个:

  • BootStrapClassLoader:负责加载标准库中的类(String,ArrayList,Random,Scanner…)
  • ExtensionClassLoader:负责加载JDK扩展的类
  • ApplicationClassLoader:负责加载当前项目目录的类

程序员还可以自定义加载器,来加载目录中的类,Tomcat就自定义了类加载器,用来专门加载webapps里面的.class。

双亲委派模型就描述了这个找目录过程。
在这里插入图片描述

1)加载java.lang.String

程序启动先进入ACL类加载器

  1. ACL就会检查下,它的附加在其是否已经加载过了,如果没有就调用父“类加载器”ECL
  2. ECL也会检查下,它的父“加载器”是否已经加载过了,如果没有就调用父“类加载器”BSCL
  3. BSCL也会检查下,它的父“加载器”是否已经加载过了,没有父亲于是扫描自己负责的目录
  4. java.lang.String这个类在标准库能找到,直接由BSCL负责后续加载过程。

2)加载自己写的Test类

在这里插入图片描述

程序启动先进入ACL类加载器

  1. ACL就会检查下,它的附加在其是否已经加载过了,如果没有就调用父“类加载器”ECL
  2. ECL也会检查下,它的父“加载器”是否已经加载过了,如果没有就调用父“类加载器”BSCL
  3. BSCL也会检查下,它的父“加载器”是否已经加载过了,没有父亲于是扫描自己负责的目录,没有扫描到回到子加载器继续扫描
  4. ECL也会扫描下自己负责的目录,也没扫描到回到子加载器继续扫描
  5. ACL也会扫描下自己负责的目录,能找到Test类,于是进行后续加载。

设计原因:一旦程序员自己写的类和标准库中的类,全限定类名重复了也能够顺利加载到标准库中的类。自定义的类加载器可以遵守,也可以不遵守双亲委派,tomcat加载webapp得类就不遵守。

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

1)垃圾回收的概念

垃圾回收是有运行时环境通过复杂的策略,判定内存是否可以回收并进行回收的动作。垃圾回收本质上是靠运行时环境额外做了很多工作,来完成自动释放内存的操作。

劣势:
1.消耗额外开销
2.可能会影响程序的流畅运行(垃圾回收经常会引入STW问题)

垃圾回收的内容?内存
内存又分为以下几种:

  • 程序计数器:固定大小,不涉及释放,也不需要GC
  • 栈:函数执行完毕,对应的栈帧也就自动释放了,也不需要GC
  • 堆:最需要GC的,代码中的量的内存都是在堆上的
  • 方法区:类对象,类加载来的,进行“类加载”就需要释放内存,卸载操作其实是一个非常低频的操作

在这里插入图片描述

途中需要进行回收的是不再使用单时尚未回收的内存。对于一部分仍然使用,一部分不在的对象,整体来说是不是放的,等到这个对象彻底完全不使用才真正释放。

2)回收的过程:

找垃圾/判定垃圾

a.基于引用计数(Python采取的方案)
在这里插入图片描述

针对每个对象都会额外引入一小块内存,保存这个对象有多少个引用指向它,这个内存不再使用就释放了(引用计数为0)

缺点:

  • 空间利用率低,每个new的对象都得搭配个计数器(计数器假设4个字节),如果对象本身很大(几百个字节),多出来的4个字节就可以忽略。但是如果对象本身很小(自己才4个字节),多出4个字节相当于空间被浪费了一倍。
  • 会有循环引用的问题

例子:

class Test{
	Test t =null;
}

Test t1 = new Test();
Test t2 = new Test();

t1.t = t2;
t2.t = t1;

t1 = null;
t2 = null;

在这里插入图片描述

此时t1,t2,以及两个对象的状态如上图所示,两个对象的引用计数不为0,所以无法释放。但是由于引用长在彼此身上,外界的代码无法访问到这两个对象。此时此刻这两个对象就被孤立了,既不能使用又不能释放,这就出现了内存泄露的问题。

b.基于可达性分析(java采取的方案)

通过额外的线程,定期的针对整个内存空间的对象进行扫描,由一些起始位置(称为GCRoots),会类似于深度优先遍历一样,把可以访问到的对象都标记一遍(带有标记的对象就是可达的对象),没被标记的对象就是不可达对象(垃圾)。

GCRoots:

  • 栈上的局部变量
  • 常量池中的引用指向的对象
  • 方法区中静态成员指向

优点:空间利用率低,解决了循环引用的问题
缺点:系统开销大,遍历一次可能比较慢

释放垃圾

a.标记清除
在这里插入图片描述

标记就是可达性分析的过程,清楚就是直接释放内存。此时如果直接释放虽然内存是还给系统了,但是释放的内存是离散的会带来“内存碎片”问题。

b.复制算法
在这里插入图片描述

复制算法是为了解决内存碎片的问题,用一半丢一半直接把不是垃圾的拷贝到另一半,把原来整个空间整体都释放掉。此时内存碎片的问题就迎刃而解了。

缺点:内存空间利用率低,如果要保留的对象多,释放的对象少,复制开销就很大。

c.标记整理
针对复制算法做出的改进
在这里插入图片描述

原理类似于顺序表删除中间元素,有一个搬运操作。这个方案空间利用率是高了,但是仍然没有解决复制/搬运元素开销大的问题

d.综合使用(JVM)

实际的JVM中的实现会把多种方案结合起来使用,即“分代回收”,针对对象进行分类。根据对象“年龄”分类,一个对象熬过一轮GC的扫描就称为“长了一岁”,针对不同年龄采取不同方案。

在这里插入图片描述

  1. 刚创建出来的对象就放在伊甸园
  2. 如果伊甸园的对象熬过一轮GC扫描,就会被拷贝到幸存区(应用复制算法)
  3. 在后续的几轮GC中,幸存区的对象就在两个幸存区之间来回拷贝(复制算法),每一轮都会淘汰掉一波幸存者
  4. 在后续若干轮后进入老年代,老年代的特点:对象年龄大,而且经验可得对象年龄越大,存活的可能性也就越大,因此老年代的GC扫描频率大大低于新生代。老年代使用标记整理方式进行回收。

3)垃圾收集器

比较老的垃圾收集器

  • Serial收集器,Serial Old收集器,串行收集即在进行垃圾扫描和释放的时候业务要停止工作,这种方式扫描的慢释放的也慢,也产生严重的STW。
  • ParNew收集器(新生代),Parallel Old收集器(老年代),并发手机引入多线程。

新的收集器
1)CMS收集器

  1. 初始标记速度很快,会引起短暂的STW(只是找到GCRoots)
  2. 并发标记,虽然速度慢但是可以和业务线程并发执行,不会产生STW
  3. 重新标记在b业务代码可能会影响并发标记的结果,对b结果的微调,虽然会引起STW但只是微调速度快 回收内存也是和业务线程并发

2)G1收集器(唯一一款全区域的垃圾回收器,Java11de JVM)

把整个内存分成了很小的区域Region,给这些Region进行了不同的标记,有的Region方新生代对象,有的对象放老年代对象。然后在扫描的时候一次扫若干Region(不追求一轮GC就扫描完分多次扫),相比CMS对于业务代码影响更小。G1在当下可以优化到让STW停顿时间小于1ms。

相关文章:

  • 画画用电容笔还是触控笔?电容笔10大品牌排行榜
  • 企业选择快鲸开源scrm系统的7大理由
  • 中国出口商对人民币波动持乐观态度!贬值“不会继续”!
  • Spring5总结
  • 虚拟内存地址和物理内存地址?为什么我们程序里地址连续?为什么需要TLB Translation lookaside buffer
  • js高级属性
  • ISP和IAP介绍
  • 基于R语言、MATLAB、Python机器学习方法与案例分析
  • 基于php+MYSQL的旅游景点攻略的设计与实现毕业设计源码301216
  • 猿创征文|【Typescript入门】常用数据类型(2)
  • 上新啦!腾讯云云原生数据湖产品DLC 2.2.5版本发布,来看特性详解!
  • 【观察】赋能中小企业驶入成长“快车道”,华为云云商店背后的三重新价值...
  • TVM 学习指南(个人版)
  • pringboot+高考志愿填报信息管理系统 毕业设计-附源码251922S
  • python基于django的花卉种植技术交流系统
  • 【笔记】你不知道的JS读书笔记——Promise
  • C++11: atomic 头文件
  • JS基础篇--通过JS生成由字母与数字组合的随机字符串
  • ng6--错误信息小结(持续更新)
  • node 版本过低
  • Object.assign方法不能实现深复制
  • PAT A1050
  • tensorflow学习笔记3——MNIST应用篇
  • 第十八天-企业应用架构模式-基本模式
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 对象管理器(defineProperty)学习笔记
  • 猴子数据域名防封接口降低小说被封的风险
  • 前端之React实战:创建跨平台的项目架构
  • 如何使用 JavaScript 解析 URL
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 说说动画卡顿的解决方案
  • 思维导图—你不知道的JavaScript中卷
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • ​学习一下,什么是预包装食品?​
  • #QT(串口助手-界面)
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (pojstep1.3.1)1017(构造法模拟)
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (四)Controller接口控制器详解(三)
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .naturalWidth 和naturalHeight属性,
  • .net framework4与其client profile版本的区别
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .Net6使用WebSocket与前端进行通信
  • .NET开发者必备的11款免费工具
  • .NET与 java通用的3DES加密解密方法
  • [ 第一章] JavaScript 简史
  • [ 环境搭建篇 ] 安装 java 环境并配置环境变量(附 JDK1.8 安装包)
  • [<死锁专题>]