Java虚拟机(JVM)知识点总结

一. Java内存区域

1. JVM的内存区域划分,以及各部分的作用

可分为运行时数据区域和本地内存,按照线程私有线程共享分类:

线程私有:程序计数器、虚拟机栈、本地方法栈。

线程共享:堆、方法区、直接内存。

JDK1.7与1.8版本略有不同。

1.8中,方法区被划分到了本地内存,并以元空间的形式存在。

(1)程序计数器

主要用来依次读取指令,实现代码的流程控制;同时还可以记录当前线程的位置,使线程切换后能够恢复到正确的位置。

(2)虚拟机栈

主要用来实现Java方法的调用与执行。栈由多个栈帧组成, 每个栈帧由局部变量表、操作数栈、动态链接、方法返回地址构成。

(3)本地方法栈

主要用来实现本地方法的调用与执行。

(4)堆

线程共享的一块内存区域,主要用于存放新创建的对象实例,几乎所有对象都在这里分配内存;也是垃圾回收的主要区域,也可称为GC堆。

从垃圾回收的角度来说,堆还可细分为新生代和老年代。再细分的话,新生代有Eden区、Survivor区、Old区。

下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。

JDK1.8版本后,永久代被元空间取代。

对象的存活的区域大致可以描述为,新创建的对象在Eden区,当进行一次新生代垃圾回收后,如果对象还存活,就会进入Survivor区(S0、S1),如果年龄继续增加,就会进入老年代。

(5)方法区

JVM运行时数据区的一块逻辑区域,被线程共享。主要用来存放类信息、方法信息、常量、静态变量等。

永久代和元空间是方法区的具体实现。

 为什么要将永久代替换为元空间?

  • 永久代有一个JVM本身设置的固定大小上限,无法调整,元空间使用的本地内存,虽然也可能会发生内存溢出,但概论相对较小。
  • 元空间里存放的是类的元数据,由系统实际可用的空间来控制,能够加载更多的类。

(6)运行时常量池

用于存放编译期生成的各种字面量和符号引用的常量池表,常量池表会在类加载后存到方法区的运行时常量池中,它的功能类似于符号表。

(7)字符串常量池

为了提升性能与减少内存消耗,避免字符串的重复创建。

JDK1.7之前, 字符串常量池存放在永久代中, 为什么1.7后移动到堆中?

主要是因为永久代(方法区的具体实现)的垃圾回收效率太低,只有在Full GC时才会被GC。Java中有大量的字符串等待回收,放到堆中能够及时有效的回收字符串内存。

(8)直接内存

位于本地内存中,并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。

2. Java对象的创建过程

(1)类加载检查

new指令——检查指令参数是否能在常量池中定位到符号引用——检查符号引用代表的类是否被加载过——若没有——执行类加载过程。

(2)分配内存:

类加载通过后进行内存分配,两种方式,指针碰撞和空闲列表。选择哪种方式由Java堆是否完整决定,Java堆是否完整由采用的GC收集器是否具有压缩整理功能决定。

  • 指针碰撞

适用于堆完整。原理是用过的内存整合到一边,没用过的整合到另一边,中间有个分界指针,向着没用过的方向移动指针即可。

  • 空闲列表

适用于堆不完整。虚拟机会维护一个列表,列表中会记录哪些内存块可用,分配时,会找一个足够大的内存块分配给对象,然后更新列表。

内存分配并发问题:

  • CAS+失败重试:乐观锁。失败就重试,直到成功。这种方式保证了操作的原子性。
  • TLAB:在Eden区预留一块内存。分配内存时,先从TLAB中分配,如果不够,再用上述CAS进行分配。

(3)初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值,保证了对象的实例字段在代码中可以不被赋值就直接使用。

(4)设置对象头

虚拟机对对象进行必要的设置,如对象是哪个类的实例、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象头中。

(5)执行init方法

从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,init方法还没执行,所有字段都还为零。执行new指令后接着执行init方法,按照开发者的意愿进行初始化。

3. 什么是JVM堆溢出和栈溢出?

堆溢出指的是JVM的堆内存不足以分配新的对象时发生的溢出。

栈溢出指的是JVM虚拟栈空间不足以支持新的方法调用时发生的溢出。

二. JVM垃圾回收机制

1. 堆内存的常见分配策略

  • 对象优先在在Eden区分配。
  • 大对象直接进入老年代。
  • 长期存活的对象将进入老年代。

2. 死亡对象判断方法(是否可以被GC回收)

(1)引用计数法

给对象中添加一个引用计数器。

  • 每当有一个地方引用它,计数器就加 1;
  • 当引用失效,计数器就减 1;
  • 任何时候计数器为 0 的对象就是不可能再被使用的。

实现简单,效率高,但使用较少,因为无法解决对象间的循环引用问题(两个对象互相引用导致计数器不为0)。

(2)可达性分析法

以 "GC Roots" 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。

哪些对象可以作为GC Roots呢?

  • 虚拟机栈(栈帧中的局部变量表)中引用的对象。
  • 本地方法栈(Native方法)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。

3. Java中四种引用类型

(1)强引用:大部分引用都是强引用,最普遍的引用。new出来的对象都是强引用,即使内存空间不足,也不会被GC回收。

(2)软引用:SoftReference修饰。比强引用弱一些。内存空间不足时才会回收它。

(3)弱引用:WeakReference修饰。比软引用弱。无论内存空间充足与否,只要发现了弱引用就会被GC回收。

(4)虚引用:PhantomReference修饰。最弱的引用。没有实际作用,任何时候都能被回收,主要用来跟踪对象被垃圾回收的活动。

实际中使用软引用较多,软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出等问题的产生

4. 如何判断一个常量是废弃常量?

运行时常量池主要被回收的是废弃常量。

假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量。

5. 如何判断一个类是无用类?

方法区主要被回收的是无用的类。

需同时满足3个条件:

  • 该类所有的实例都已被回收。
  • 加载该类的ClassLoader已被回收。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

6. 4种垃圾收集算法

(1)标记—清除算法

标记和清除两个阶段。首先标记出所有不需要回收的对象,然后标记完成后对未标记的对象统一回收。这是基础算法,后续算法都是对其的改进。

但存在两个明显问题:效率问题(两阶段效率都不高)和空间问题(清除后存在大量不连续的内存碎片)。

(2)复制算法

为了解决效率和内存碎片问题,复制算法将内存分为大小相同的两块,每次使用其中的一块,其中一块使用完成后,将还存活的对象复制到另一块中去,然后再把使用的空间进行清理。保证每次的内存回收都是对一半区域的回收。

但依然存在问题:可用内存缩小为原来的一半。

(3)标记—整理算法

标记过程与标记—清除算法一样,但在标记完成后,让所有存活的对象向一端移动,然后清理掉端边界以为的内存。

(4)分代收集算法

主流算法。根据对象存活周期的不同将内存分为几块,一般将Java堆分为新生代和老年代,根据各个年代的特点选择合适的回收算法。

比如新生代,对象创建的多,但回收时死去的对象也很多,因此可以选择复制算法。老年代中的对象存活比较多,所以选择标记—清除或标记—整理算法。

7. 8个垃圾收集器

(1)Serial收集器

最基本、最悠久。单线程收集器,在进行垃圾收集工作时会暂停其它所有工作线程(Stop The World),直到结束。

简单而高效。

(2)ParNew收集器

Serial收集器的多线程版本,其余都和Serial一样。

(3)Parallel Scavenge收集器

几乎和ParNew收集器一样,但更关注吞吐量,高效利用CPU。

(4)Serial Old收集器

Serial收集器的老年代版本。

(5)Parallel Old收集器

Parallel Scavenge收集器的老年代版本。

(6)CMS收集器

CMS(Concurrent Mark Sweep ),以获取最短回收停顿时间为目标的收集器,注重用户体验。第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程基本上同时工作

标记清除算法的实现,运作过程大致分为四个步骤:

  • 初始标记:暂停其它所有线程,记录下与GC Root相连的对象。
  • 并发标记:同时开启GC和用户线程,用一个闭包结构记录可达对象。但GC线程无法保证可达性分析的实时性,会有引用更新的地方。
  • 重新标记:修正并发标记期间因为用户程序继续运行而导致标记产生变动的标记记录。
  • 并发清除:开启用户线程,同时GC线程对未标记的区域清除。

并发收集,低停顿。

(7)G1收集器

面向服务器的垃圾收集器,主要针对配备了多颗处理器及大容量内存的机器。既能满足低停顿还可以做到高吞吐量。

特点:

  • 并行与并发:充分利用多核的硬件优势来缩短停顿时间,也能做到和用户线程的并发执行。
  • 分代收集:不需要其它收集器配合就能独立管理整个GC堆。
  • 空间整合:整体标记—整理,局部标记—复制。
  • 可预测的停顿:除了降低停顿外,还能够建立可预测的停顿时间模型,让用户指定停顿时间。

步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region,这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率。

JDK9之后,G1变成了默认的GC。

(8)ZGC收集器

采用复制算法,将暂停时间控制在几毫米以内,且暂停时间不受内存堆大小的影响,代价是牺牲了一些吞吐量。

8. Minor GC和Full GC

Minor GC主要发生在新生代,频繁且速度快;Full GC主要发生在老年代,回收速度较慢。一般来说,对象在新生代的Eden区分配。当Eden区没有足够空间分配时,虚拟机会进行⼀次Minor GC,Minor GC之后survivor放不下,要放到老年代,此时发现老年代也放不下,就会触发Full Gc。

三. 类加载器

1. 类加载器有哪些?

类加载过程:加载—连接—初始化。

连接又分为三步:验证—准备—解析。

类加载器作用于第一步加载。

类加载器的主要作用就是加载 Java 类的字节码(. class文件)到 JVM 中(在内存中生成一个代表该类的Class对象)。

JVM中内置了三个重要的ClassLoader:

  • BootstrapClassLoader(启动类加载器):最顶层的加载类,加载核心库:JAVA_HOME/jre/lib目录下的库。
  • ExtensionClassLoader(扩展类加载器) :加载扩展类的类库;JAVA_HOME/jre/lib/ext。
  • AppClassLoader(应用程序类加载器) :面向用户的加载器,加载classpath下的类。
  • 自己编写的类加载器。

除了 BootstrapClassLoader,其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果要自定义自己的类加载器,也需要继承 ClassLoader 抽象类。

2. 双亲委派模型

类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?

双亲委派模型和上面提到的类加载器层次关系图一致。

大致流程:加载某一个类,先委托上一级的加载器进行加载,如果上级加载器也有上级,继续向上委托,如果这个类委托的上级没有被加载,子加载器会尝试加载这个类

  • 在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。
  • 加载的时候,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父类加载器的loadClass()方法来加载类),所有的请求最终都会传送到顶层的启动类加载器BootstrapClassLoader中。
  • 当父类加载器无法处理这个加载请求(它的搜索范围中没有找到所需的类),子加载器才会尝试自己加载(调用自己findClass()方法来加载类)。
  • 子加载器也无法加载,抛出ClassNotFoundException异常。

双亲委派模型的好处?

  • 保证Java程序的稳定运行,避免重复加载类(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类)
  • 保证Java的核心API不被篡改。比如自己编写了一个java.lang.Object类,程序运行时就会有两个不同的Object类。双亲委派模型可以保证加载的时JRE里的Object类,而不是自己写的。因为AppClassLoader加载自己写的Object类时,会先委派给它的父类,即ExtClassLoader,而Ext又会委派给Boot,Boot发现自己加载过Object类了,会直接返回,而不是加载自己写的。

如何打破双亲委派模型?

继承ClassLoader类,自定义一个加载器,然后重写 loadClass() 方法。

之所以重写loadClass方法是因为加载类时,类加载器会先委托父类加载器去完成,即调用父类的loadClass()方法。

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

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

相关文章

[Python GUI PyQt] PyQt5快速入门

PyQt5快速入门 PyQt5的快速入门0. 写在前面1. 思维导图2. 第一个PyQt5的应用程序3. PyQt5的常用基本控件和布局3.1 PyQt5的常用基本控件3.1.1 按钮控件 QPushButton3.1.2 文本标签控件 QLabel3.1.3 单行输入框控件 QLineEdit3.1.4 A Quick Widgets Demo 3.2 PyQt5的常用基本控件…

Redis入门到实战-第五弹

Redis入门到实战 Redis中Hashes数据类型常见操作官网地址Redis概述Hashes常见操作更新计划 Redis中Hashes数据类型常见操作 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 R…

Chrome 插件各模块使用 Fetch 进行接口请求

Chrome 插件各模块使用 Fetch 进行接口请求 常规网页可以使用 fetch() 或 XMLHttpRequest API 从远程服务器发送和接收数据,但受到同源政策的限制。 内容脚本会代表已注入内容脚本的网页源发起请求,因此内容脚本也受同源政策的约束,插件的来…

k8s入门到实战(十四)—— Helm详细介绍及使用

Helm 使用 Helm 是一个 k8s 应用的包管理工具,类似于 Ubuntu 的 APT 和 CentOS 中的 YUM。 Helm 使用 chart 来封装 k8s 应用的 yaml 文件,我们只需要设置自己的参数,就可以实现自动化的快速部署应用。 Helm 通过打包的方式,支…

Spring 源码调试问题 ( List.of(“bin“, “build“, “out“); )

Spring 源码调试问题 文章目录 Spring 源码调试问题一、问题描述二、解决方案 一、问题描述 错误&#xff1a;springframework\buildSrc\src\main\java\org\springframework\build\CheckstyleConventions.java:68: 错误: 找不到符号 List<String> buildFolders List.of…

【问题分析】InputDispatcher无焦点窗口ANR问题【Android 14】

1 问题描述 Monkey跑出的无焦点窗口的ANR问题。 特点&#xff1a; 1&#xff09;、上层WMS有焦点窗口&#xff0c;为Launcher。 2&#xff09;、native层InputDispacher无焦点窗口&#xff0c;上层为”recents_animation_input_consumer“请求了焦点&#xff0c;但是”rece…

整理git上的模板框架

vite-vue3.0-ts-pinia-uni-app 技术栈的app框架 功能&#xff1a;基于 uni-app&#xff0c;一端发布多端通用&#xff0c;目前已经适配 H5、微信小程序、QQ小程序、Ios App、Android App。 taro3vue3tsnutuipinia taro3 框架小程序跨端平台 vue3.0-element-vite-qiankun 后台…

HarmonyOS实战开发-如何构建多种样式弹窗

介绍 本篇Codelab将介绍如何使用弹窗功能&#xff0c;实现四种类型弹窗。分别是&#xff1a;警告弹窗、自定义弹窗、日期滑动选择器弹窗、文本滑动选择器弹窗。需要完成以下功能&#xff1a; 点击左上角返回按钮展示警告弹窗。点击出生日期展示日期滑动选择器弹窗。点击性别展…

计算机xinput1_3.dll丢失的解决方法,分享5种有效的解决方法

在计算机系统的运行过程中&#xff0c;当用户或应用程序试图访问某个特定功能时&#xff0c;可能会遇到“找不到xinput1_3.dll”这一错误提示。这个问题通常是由于系统文件缺失或损坏导致的。许多依赖于“xinput1_3.dll”文件的游戏、软件或工具在启动时会立即遭遇阻碍。由于该…

rust使用Command库调用cmd命令或者shell命令,并支持多个参数和指定文件夹目录

想要在不同的平台上运行flutter doctor命令&#xff0c;就需要知道对应的平台是windows还是linux&#xff0c;如果是windows就需要调用cmd命令&#xff0c;如果是linux平台&#xff0c;就需要调用sh命令&#xff0c;所以可以通过cfg!实现不同平台的判断&#xff0c;然后调用不同…

Excel·VBA数组分组问题

看到一个帖子《excel吧-数据分组问题》&#xff0c;对一组数据分成4组&#xff0c;使每组的和值相近 目录 代码思路1&#xff0c;分组形式、可分组数代码1代码2代码2举例 2&#xff0c;数组所有分组形式举例 这个问题可以转化为2步&#xff1a;第1步&#xff0c;获取一组数据…

QT 控件有突出感,定义控件边框

QT 控件有突出感&#xff0c;定义控件边框 1.设计师页面 在flat部分选中 这个时候按钮会失去边框如下图&#xff1a; 然后在.cpp文件中写入代码&#xff1a; ui->pushButton->setStyleSheet("border: 1px solid gray;");按钮就有了新的边框&#xff1a;

C++11入门手册第一节,学完直接上手Qt(共两节)

入门 hello.cpp #include <iostream>int main() { std::cout << "Hello Quick Reference\n"<<endl; return 0;} 编译运行 $ g hello.cpp -o hello$ ./hello​Hello Quick Reference 变量 int number 5; // 整数float f 0.95; //…

快排(六大排序)

快速排序 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右子序列中所…

yolov8本地、autodl环境配置、训练

目录 搭建本地环境安装miniconda3创建一个新的环境安装包 安装pycharm下载汉化导入解释器测试终端终端运行代码 YOLOv8本地训练数据集制作训练文件 YOLOv8 autodl训练流程注册账号充值选则服务器jupyterlab创建训练环境上传文件训练使用vscode SSH使用pycharm专业版SSH下载文件…

最大子序列(蓝桥杯,acwing,单调队列)

题目描述&#xff1a; 输入一个长度为 n 的整数序列&#xff0c;从中找出一段长度不超过 m 的连续子序列&#xff0c;使得子序列中所有数的和最大。 注意&#xff1a; 子序列的长度至少是 1。 输入格式&#xff1a; 第一行输入两个整数 n,m。 第二行输入 n 个数&#xff0…

Matlab|电动汽车充放电V2G模型

目录 1 主要内容 1.1 模型背景 1.2 目标函数 1.3 约束条件 2 部分代码 3 效果图 4 下载链接 1 主要内容 本程序主要建立电动汽车充放电V2G模型&#xff0c;采用粒子群算法&#xff0c;在保证电动汽车用户出行需求的前提下&#xff0c;为了使工作区域电动汽车尽可能多的消…

Mojo与Python——wsl安装mojo

文章目录 前言一、wsl设置二、安装步骤三、mojo初体验四、vscode联合开发总结 前言 此课程为系列课程&#xff0c;借助python语言来学习python语言的超集mojo。可以持续关注。 一、wsl设置 powershell查看wsl的版本&#xff0c;如果版本是1需要修改为2。 二、安装步骤 1.安装m…

浅试Kimi

最近KIMI大模型挺火的&#xff0c;擅长处理中文文本&#xff0c;咱也来试试吧&#xff01; 测试问题&#xff1a; 写一篇800字以上的短片小说&#xff1a;主要故事是以一位上进但其他方面表现平平的大男孩小贱&#xff0c;刚到公司不久&#xff0c;就被一位名叫大弟的女同事看…

HarmonyOS 应用开发之多端协同

多端协同流程 多端协同流程如下图所示。 图1 多端协同流程图 约束限制 由于“多端协同任务管理”能力尚未具备&#xff0c;开发者当前只能通过开发系统应用获取设备列表&#xff0c;不支持三方应用接入。 多端协同需遵循 分布式跨设备组件启动规则。 为了获得最佳体验&…