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.rhkb.cn/news/291134.html

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

相关文章

vant2调用Dialog、Toast报错 is not defined

报错 ReferenceError: Dialog is not defined 照着官方文档,运行会报错 经过测试发现 main.js并不需要引入Dialog,而是在具体组件中单独引入即可,确实挺坑的!记录一下!!!

SpringBoot实现RabbitMQ延迟队列

RabbitMQ实现延迟队列的两种方式 利用RabbitMQ插件方式实现延迟队列利用RabbitMQ死信队列实现延迟队列 插件方式实现延迟队列 下载插件:Community Plugins | RabbitMQ 按照官网步骤安装插件 installing Additional Plugins | RabbitMQ 插件方式实现延迟队列&a…

element-ui-plus el-tree 树形结构如何自定义内容

element-ui-plus el-tree 树形结构如何自定义内容 本文提及的 elementUI 版本 为 elementUI Plus 版本 一、需求 项目中遇到一个需要设置权限的地方,但目录和权限是放在一起的,这样就很不好区分类别,为了区分类别,就需要自定义树…

【C语言】结构体详解 (二) 内存函数、结构体传参

目录 1、 结构体的内存对齐 1.1、对齐规则 1.2、练习1、练习2(演示对齐规则1、2、3、4) 2、为什么存在内存对齐 2.1、平台原因(移植原因) 2.2、性能原因 2.3、那么如何即满足对齐,又要节省空间呢? …

【Vue3源码学习】— CH2.5 reactiveEffect.ts:Vue 3响应式系统的核心

reactiveEffect.ts:Vue 3响应式系统的核心 1. 什么是 reactiveEffect?2. 核心机制2.1 依赖收集(Track)2.2 触发更新(Trigger)2.3 效果范围(effectScope) 3. 源码解析 —— track3.1 …

卷积神经网络(CNN)——基础知识整理

文章目录 1、卷积神经网络 2、图片格式 3、图片卷积运算 4、Kernel 与 Feature Map 5、padding/边缘填充 6、Stride/步长 7、pooling/池化 8、shape 9、epoch、batch、Batch Size、step 10、神经网络 11、激活函数 1、卷积神经网络 既然叫卷积神经网络,这里面首先是…

4、Cocos Creator 动画系统

目录 1、Clip 参数 2、动画编辑器 3、基本操作 更改时间轴缩放比例 移动显示区域 更改当前选中的时间轴节点 播放 / 暂停动画 修改 clip 属性 快捷键 4、模拟实验 5、动画事件 6、注意事项 参考 Animation 组件是节点上的一个组件。Clip 动画剪辑就是一份动画的声…

67、yolov8目标检测和旋转目标检测算法部署Atlas 200I DK A2开发板上

基本思想:需求部署yolov8目标检测和旋转目标检测算法部署atlas 200dk 开发板上 一、转换模型 链接: https://pan.baidu.com/s/1hJPX2QvybI4AGgeJKO6QgQ?pwdq2s5 提取码: q2s5 from ultralytics import YOLO# Load a model model YOLO("yolov8s.yaml")…

物流监控升级,百递云·API开放平台助力某电商平台实现高效物流管理

不论是电商平台自身还是消费者,都有着对物流监控的强烈需求。 消费者下单后be like: 每十分钟看一次快递轨迹 放心,电商平台也一样关心物流状态!怎样第一时间向用户传递物流状态? 怎么减少重复的提问和投诉?怎样监管…

Collection与数据结构 顺序表与ArrayList

1. 线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在…

【Docker】搭建强大的Nginx可视化配置工具 - nginxWebUI

【Docker】搭建强大的Nginx可视化配置工具 - nginxWebUI 前言 本教程基于绿联的NAS设备DX4600 Pro的docker功能进行搭建。 简介 NginxWebUI是一个基于Java的,专门用来管理Nginx的图形界面工具。它是开源的,使用相对简单且功能全面。 使用NginxWebUI…

接劳巴,拔掉KL15,MCU重启。不接劳巴,拔掉KL15,MCU正常下电

最近遇到一个神奇的现象,在调试一个单KL15的项目,发现接着劳特巴赫调试器,然后拔掉KL15,软件进入了重启Reset函数,没有进入期望的下电SwitchOff函数。 而不接劳特巴赫,拔掉KL15,观测电流&#…

Qt实现Kermit协议

1 概述 Kermit文件运输协议提供了一条从大型计算机下载文件到微机的途径。它已被用于进行公用数据传输。 其特性如下: Kermit文件运输协议是一个半双工的通信协议。它支持7位ASCII字符。数据以可多达96字节长度的可变长度的分组形式传输。对每个被传送分组需要一个确认。Kerm…

python-判断列表字典循环

比较运算符 不等于 ! if 布尔值: [执行语句-真实执行] else: [执行语句] mood_index int(input("对象今天的心情指数的是:")) if mood_index > 60:print("恭喜,今晚应该可以带游戏,去吧")…

2024年水电站大坝安全监测工作提升要点

根据《水电站大坝运行安全监督管理规定》(国家发改委令第23号)和《水电站大坝运行安全信息报送办法》(国能安全〔2016〕261号)的相关规定、要求,电力企业应当在汛期向我中心报送每日大坝汛情。近期,全国各地…

【机器学习】深度解析KNN算法

深度解析KNN算法 KNN(K-最近邻)算法是机器学习中一种基本且广泛应用的算法,它的实现简单直观,应用范围广泛,从图像识别到推荐系统都有其身影。然而,随着数据量的增长,KNN算法面临着严峻的效率挑…

[yolox]ubuntu上部署yolox的ncnn模型

首先转换pytorch->onnx->param模型,这个过程可以查资料步骤有点多,参考blog.51cto.com/u_15660370/6408303,这里重点讲解转换后部署。 测试环境: ubuntu18.04 opencv3.4.4(编译过程省略,参考我其他博客) 安装…

【智能家居入门3】(MQTT服务器、MQTT协议、微信小程序、STM32)

前面已经写了三篇博客关于智能家居的,服务器全都是使用ONENET中国移动,他最大的优点就是作为数据收发的中转站是免费的。本篇使用专门适配MQTT协议的MQTT服务器,有公用的,也可以自己搭建(应该要钱)&#xf…

常见的数学方法

Math类表示数学类,其中的数学方法都被定义成为static形式,所以可以直接通过Math类的类名调用某个数学方法。语法格式: Math.xxx(参数); 例题 输入n个整数a1,a2,a3,......an,求这n个数的最大值max,最小值min&#xff0…

算法之并查集

并查集(Union-find Data Structure)是一种树型的数据结构。它的特点是由子结点找到父亲结点,用于处理一些不交集(Disjoint Sets)的合并及查询问题。 Find:确定元素属于哪一个子集。它可以被用来确定两个元…