JVM学习与理解

news2025/2/19 9:12:35

目录

JVM介绍:

解释:

特点:

整体构成:

执行过程: 

运行时数据区:

Java堆剖析:

堆内存区域划分

为什么要分代呢?

内存分配:

新生区与老年区配置比例:

分代收集思想 Minor GC、Major GC、Full GC:

堆空间的参数设置 :

方法区:

方法区大小:

垃圾回收:

垃圾回收: 

GC主要干什么?

垃圾回收相关算法:

垃圾标记阶段算法

对象的 finalization 机制 :

垃圾回收阶段算法:

标记-复制算法 :

标记-清除算法: 

标记-压缩算法:

内存泄漏:

内存溢出: 

Stop the World:

JVM常见问题以及决解方法:

1. 内存溢出(OutOfMemoryError):

2. JVM 启动慢或性能瓶颈:

3. 线程问题(死锁、线程过多):

4.内存泄漏(Memory Leak):

5.JVM 启动失败(JVM Crash):


JVM介绍:

解释:

Java虚拟机,Java的所有程序都会在这个虚拟机上执行。

特点:

1. 一次编译到处运行

2.自动内存管理

3.自动垃圾回收

整体构成:

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

执行过程: 

1. java 代码转换成字节码(class 文件)

2. jvm 首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中的运行时数据区(Runtime Data Area)

3. 而字节码文件是 jvm 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU 去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能。
       通常所说的 JVM 组成指的是 运行时数据区(Runtime Data  Area) ,因为通常需要调试分析的区域就是“运行时数据区”,或者更具体的来说就是“运行时数据区”里面的 Heap(堆)模块

接下来我将直接介绍运行时数据区:

运行时数据区:

  •  程序计数器(Program Counter Register)

       程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

  • Java 虚拟机栈(Java Virtual Machine Stacks)

       描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个线帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,都对应着一个线帧在虚拟机栈中入栈到出栈的过程。

  • 本地方法栈(Native Method Stack)

       与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的

  • Java 堆(Java Heap)

      是 Java 虚拟机中内存最大的一块,是被所有线程共享的,在虚拟机启动时候创建,Java 堆唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存.

  • 方法区(Methed Area)

       用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。方法区是很重要的系统资源,是硬盘和 CPU 的中间桥梁,承载着操作系统和应用程序的实时运行.

线程间共享:堆,方法区. 线程私有:程序计数器,栈,本地方法栈. 

Java堆剖析:

  1.  堆在jvm中只存在一个实例,它被所有线程所共享。
  2. 堆可以设置大小Xms(堆的起始大小)-----Xmx(堆的最大值),但一般设置起始值和最大值相等,这样就可以减少GC(垃圾回收)后的内存程序分配,可以提高效率。
  3. 堆可以是在物理上不连续的空间,但在逻辑上是连续的。
  4. 方法结束后,堆中的对象会马上失效,但是不会马上被移除,只有GC后才会移除。
  5. 堆是GC(垃圾回收器)重点回收的区域。
  6. 所有对象实例都在运行时分配到堆上。

堆内存区域划分

Java8 及之后堆内存分为 :新生区(新生代)+老年区(老年代)

新生区分为 Eden(伊甸园)区和 Survivor(幸存者)区

为什么要分代呢?

        将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及 GC 频率。

       针对分类进行不同的垃圾回收算法,对算法扬长避短。

内存分配:

  1.  new的对象会被放到新生区的Eden区,该区域有大小限制。
  2. 当Eden区满了后,又有新的对象要被创建,这时候就会触发GC,对Eden区进行垃圾回收,将没有被引用的对象进行回收,这时候就为新的对象清理出新的空间。
  3. 这时候如果没有被GC回收的,会被放入到Survivor0区当中。
  4. 当下一次触发垃圾回收时,Survivor0区当中没有被清理的对象会被移动到Survivor1区当中,每次都会保证他们2个当中有一个幸存者区是空的。
  5. 再次经历垃圾回收,剩余的对象又会被从Survivor1区当放入到Survivor0区当中。
  6. 那什么时候去养老去呢?这个我们是可以通过-XX:MaxTenuringThreshold=<N>参数设置的,默认是15次垃圾回收后,对象头当中有4位数据对GC年龄进行保存,1111,所有默认是15次。
  7. 当老年区是相对很悠闲的,当老年区满了后会再次触发垃圾回收,如果回收后还是满的话,就会产生 OOM 异常. Java.lang.OutOfMemoryError:Java heap space。内存溢出。

新生区与老年区配置比例:

配置新生代与老年代在堆结构的占比(一般不会调)
默认**-XX:NewRatio**=2,表示新生代占 1,老年代占 2,新生代占整个堆的 1/3
可以修改**-XX:NewRatio**=4,表示新生代占 1,老年代占 4,新生代占整个堆的 1/5
当发现在整个项目中,生命周期长的对象偏多,那么就可以通过调整老年代的大小,来进行调优

分代收集思想 Minor GC、Major GC、Full GC:

 JVM在GC时,并非是每次都新生区的老年区一起回收,更多的是对新生区进行回收,针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类型:一种是部分收集,一种是整堆收集。

部分收集,不是整个Java堆的收集:分为:老年区收集,新生区(Eden,S0,S1)收集 。

整堆收集:对整个Java堆和方法区的垃圾进行收集。 整堆收集的情况分为:system.gc;不足时;老年区空间不足时;方法区空间不足时;开发期间应该避免整堆收集。

堆空间的参数设置 :

-XX:+PrintFlagsInitial   查看所有参数的默认初始值
-XX:+PrintFlagsFinal   查看所有参数的最终值(修改后的值)
-Xms   初始堆空间内存(默认为物理内存的 1/64)
-Xmx   最大堆空间内存(默认为物理内存的 1/4)
-Xmn   设置新生代的大小(初始值及最大值)
-XX:NewRatio   配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio   设置新生代中 Eden 和 S0/S1 空间比例
-XX:MaxTenuringTreshold   设置新生代垃圾的最大年龄
XX:+PrintGCDetails   输出详细的 GC 处理日志

方法区:

        方法区用来保存类,如果系统定义了太多的类,导致方法区溢出, 虚拟机同样会抛出内存溢出的错误。

方法区大小:

  1. 方法区大小也是不一定是固定的,可以设置大小。
  2. 他的大小可以由-XX:MetaspaceSize 和 -XX:MaxMataspaceSize 指定。
  3. 默认值依赖于平台,windows 下,-XXMetaspaceSize 是 21MB。
  4. -XX:MaxMetaspaceSize 的值是-1,最大值默认为无限制。
  5. 而-XX:MetaspaceSize 初始值是 21M 也称为高水位线 一旦触及就会触发 Full GC。
  6. 为了避免频繁的Full GC,我们可以把-XX:MetaspaceSize 初始值设置大一点。

垃圾回收:

方法区的垃圾收集主要回收两部分内容:运行时常量池中废弃的常量不再使用的类型

废弃常量:没有任何对象引用它。

废弃类:

  1. 该类的所有实例被回收。
  2. 该类的类加载器被回收。
  3. 该类对象没有在任何地方被引用。

 

垃圾回收: 

垃圾是指没有被任何引用指向的对象,如果不对垃圾进行清理,那么垃圾就会一直占用内存空间,直到应用结束,导致其他对象使用不到内存空间,甚至内存溢出。

GC主要干什么?

它将不被使用到的对象释放,也可以清理内存里的记录碎片,将所有的对象移动到堆的一段。

垃圾回收相关算法:

垃圾标记阶段算法

  1. 垃圾标记阶段算法:
    那些不再被使用到的对象就被标记为垃圾,只有标记为垃圾对象,在GC阶段才会被回收。
    标记垃圾对象的算法有2种:引用计数算法和可达性分析算法
    *  引用计数算法
    每个对象都有一个引用计数器属性,只要对象被引用1次,计数器就会+1。但会增加存储空间的消耗,并且每次计数都会增加时间的消耗,最重要的是,如果2个对象相互引用,形成循环的话,是无法处理的。

    *  可达性分析算法
    可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
    基本思路如下:
    1. 从根"GCRoots”开始,从上至下的搜索被根对象连接到目标对象是否可达 。
    2. 使用可达性分析算法后,目标内存中的存活对象都会被根直接或间接连接着,搜索走过的路径成为引用链。
    3.没有被引用链相连的就是不可达的,标记为垃圾对象。

    ”GCRoots“可以由:如:方法区中所有类的静态成员变量(包括静态字段); 常量池中的字符串常量、数字常量; 当前活动的线程在执行过程中可能会引用其他对象,因此活动线程可以作为 GC Roots; 被同步锁 synchronized 持有的对象; 基 本 数 据 类 型 对 应 的 Class 对 象 , 一 些 常 驻 的 异 常 对 象 ( 如 : NullPointerException、OutofMemoryError)。

对象的 finalization 机制 :

Java提供对象终止机制,它是对象销毁之前的自定义处理逻辑。对象销毁之前终会先调用对象的finalize()方法,每个finalize()方法只能被调用一次。
finalize()方法它允许被子类重写,一般是用来释放资源、清理工作,比如关闭文件,套接字,数据库连接等。

为此,定义虚拟机中的对象可能的三种状态 。如下:

  1. 可触及状态:从根节点可触及
  2. 可复活状态:对象的所有引用都被释放,但在finalize()方法中可能被复活
  3. 不可触及状态:对象的 finalize()被调用,并且没有复活,那么就会进入不可触及状态。

上面三种状态,当finalize()存在进行区分,只有不可复活状态才会被回收。

例:判断一个对象A是否被回收,至少需要判断2次:

  1. 判断根节点无法触及对象,标记为一次。
  2. 判断对象的finalize()方法,有没有必要执行:
    ①对象的finalize()方法没有被重写,或者finalize()方法已经被执行了,虚拟机认为没有必要执行,A就被判定为不可触及状态
    ②对象的finalize()方法被重写,并且还没有被执行,会被加入到finalizer队列当中去等待执行finalize()方法,当执行finalize()方法时,如果与任何对象建立联系,那么A对象就会被移除“即将回收”集合。当下次这个情况,finalize()方法不会被再次调用,而是会直接判定为不可触及状态。

垃圾回收阶段算法:

标记-复制算法 :

        它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。

优点:

  • 没有标记和清除阶段,运行简单。
  • 复制过去后保证空间的连续性,不会出现“碎片”问。

缺点:

  • 需要2倍的内存空间。

应用场景:

一般用在新生代当中,因为新生代中垃圾对象很多,需要复制的对象相对较少,这样效率较高。

标记-清除算法: 

执行过程
当堆中的有效内存空间被耗尽的时候,然后进行这项工作.
        标记:在这个阶段,JVM会遍历所有的对象,标记那些被根对象(GC Root)可达的对象。
       清除:在标记阶段完成后,JVM会遍历堆中的所有对象,清除那些未被标记的对象。未被标记的对象意味着它们不再被任何根对象引用,因此可以回收它们的内存。

标记-压缩算法:

第一阶段和标记清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。

优点:

  1. 消除了复制算法当中,内存减半的高额代价。
  2. 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。

缺点:

  1. 移动过程中,需要全程暂停用户应用程序。即:STW
  2. 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址

内存泄漏:

内存溢出是指程序在运行过程中请求的内存超过了JVM可用内存的限制,导致无法为新的对象分配内存。JVM会抛出 OutOfMemoryError 错误,表示堆内存或其他区域的内存不足,无法继续分配。

内存溢出: 

内存泄漏是指程序中已经不再使用的对象由于某些原因,仍然持有引用,导致这些对象无法被垃圾回收器回收,从而造成内存资源的浪费。虽然程序的运行没有超出可用内存的限制,但由于内存被不断占用,最终可能导致系统性能下降,甚至内存溢出。

例:

  1. Threadlocal:
    ThreadLocal 在某些情况下会导致内存泄漏,尤其是当线程池中的线程没有被正确清除时,ThreadLocal 的数据不会被回收。
  2. 一些提供 close()的资源未关闭导致内存泄漏
    数据库连接 dataSourse.getConnection(),网络连接 socket 和 io 连接必须手动 close,否则是不能被回收的。

Stop the World:

指的是 GC 事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。

可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿:

  1. 分析工作必须在一个能确保一致性的快照中进行

JVM常见问题以及决解方法:

1. 内存溢出(OutOfMemoryError):

解决方法:

  1. 使用 -Xms-Xmx 设置堆内存的初始值和最大值,确保堆内存足够大。
  2. 启用 GC日志-Xlog:gc*),查看GC频率和停顿时间,了解垃圾回收的情况。
  3. 分析内存泄漏:可以使用 jmapHeap Dump 分析工具来识别没有被垃圾回收的对象,检查是否有不再使用的对象被错误地保留引用。
  4. 检查 JVM 配置:合理配置堆内存,避免频繁 Full GC。适当调整 新生代老年代 的比例(如 -XX:NewRatio)。

2. JVM 启动慢或性能瓶颈:

  1. 分析启动参数:使用合适的 JVM启动参数,比如 -XX:+UseG1GC-Xms-Xmx,确保堆内存设置合理。
  2. 类加载问题:应用启动时大量的类加载可能会拖慢启动速度。可以通过调整类加载器、类缓存等方式优化类加载速度。

  3. 代码优化:使用合适的数据结构和算法,减少不必要的计算,优化程序逻辑。

3. 线程问题(死锁、线程过多):

  1. 死锁通常表现为两个或多个线程互相等待对方释放锁。分析线程转储中的锁信息,找出死锁源。
  2. 检查线程池配置:确保线程池大小和队列容量合适,避免线程池溢出。可以调整 ThreadPoolExecutor 的核心线程数、最大线程数等参数。

4.内存泄漏(Memory Leak):

  1. Heap Dump 分析:在内存消耗过多时,生成 Heap Dump 文件,并使用 MATVisualVM 等工具分析内存使用情况,找出占用内存的对象。
  2. Threadlocal在线程池的情况下数据没有被清理;数据库连接,socket,io等没有close。

5.JVM 启动失败(JVM Crash):

  1. 内存配置问题:确保内存配置合理,过大的堆内存或直接内存可能导致 JVM 崩溃。
  2. JVM版本兼容性问题:确认使用的 JVM 版本与操作系统、硬件架构等兼容,确保没有已知的 JVM bug。
  3. 查看 Crash Log:JVM 崩溃时,通常会生成 hs_err_pid.log 文件,查看其中的错误堆栈信息,查找崩溃的原因。

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

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

相关文章

数据守护者:备份文件的重要性及自动化备份实践

在信息化社会&#xff0c;数据已成为企业运营和个人生活的重要组成部分。无论是企业的核心业务数据&#xff0c;还是个人的珍贵照片、重要文档&#xff0c;数据的丢失或损坏都可能带来无法估量的损失。因此&#xff0c;备份文件的重要性愈发凸显&#xff0c;它不仅是数据安全的…

初阶c语言(练习题,猜随机数,关机程序)

目录 第一题&#xff0c;使用函数编写一个随机数&#xff0c;然后自己猜&#xff0c;猜随机数 第二道题&#xff08;关机程序&#xff09; 实现代码&#xff08;关机程序&#xff09; 实现代码&#xff08;猜数字&#xff09; 前言&#xff1a; 学习c语言&#xff0c;学习…

TypeScript 与后端开发Node.js

文章目录 一、搭建 TypeScript Node.js 项目 &#xff08;一&#xff09;初始化项目并安装相关依赖 1、创建项目目录并初始化2、安装必要的依赖包 &#xff08;二&#xff09;配置 TypeScript 编译选项&#xff08;如模块解析方式适合后端&#xff09; 二、编写服务器代码 &a…

基于SSM+uniapp的鲜花销售小程序+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、商户功能模块&#xff1a;用户管理、商户管理、鲜花分类管理、鲜花管理、订单管理、收藏管理、购物车、充值、下单等技术选型&#xff1a;SSM&#xff0c;Vue&#xff08;后端管理web&#xff09;&#xff0c;uniapp等测试环境&#x…

数据开放共享和平台整合优化取得实质性突破的智慧物流开源了

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…

2025互联网医院系统源码解析:AI陪诊问诊APP的未来发展

2025年&#xff0c;AI陪诊问诊APP将如何在技术上创新&#xff0c;如何推动互联网医院的进一步发展&#xff0c;成为了我们今天探讨的核心内容。在本文中&#xff0c;我们将通过源码解析&#xff0c;深入分析这一前沿技术的未来发展趋势&#xff0c;帮助广大从业者更好地理解这一…

【NLP 22、语言模型 language model】

有时候我也想听听&#xff0c;我在你心里&#xff0c;是什么样子 —— 25.1.12 一、什么是语言模型 语言是灵活的&#xff0c;也是有规律的 了解一门语言的人可以判断一句话是否“合理” 通俗来讲&#xff0c;语言模型用来评价一句话(句子可以看作是字的组合)是否“合理”或…

(萌新入门)如何从起步阶段开始学习STM32 —— 0.碎碎念

目录 前言与导论 碎碎念 所以&#xff0c;我到底需要知道哪些东西呢 从一些基础的概念入手 常见的工具和说法 ST公司 MDK5 (Keil5) CubeMX 如何使用MDK5的一些常用功能 MDK5的一些常见的设置 前言与导论 非常感谢2301_77816627-CSDN博客的提问&#xff0c;他非常好奇…

Eclipse:关闭多余的工具条

Eclipse默认的工具条非常多&#xff0c;可以通过如下方法选择关闭一些不常用的&#xff1a; 1.选择菜单Window -> Perspective -> Customize Perspective 2.根据需要勾选Toolbar Visbility下面的工具条项

【第3章:卷积神经网络(CNN)——3.6 CNN的高级特性与优化策略】

在2012年ImageNet竞赛的颁奖现场,当AlexNet以超出第二名10%的惊人准确率夺冠时,整个计算机视觉界都意识到:这个叫CNN的架构正在重写游戏规则。十年后的今天,当我们站在YOLOv8、Vision Transformer等新架构的肩膀上回望,会发现经典CNN的进化史就是一部浓缩的深度学习发展史…

【R语言】非参数检验

一、Mann-Whitney检验 在R语言中&#xff0c;Mann-Whitney U检验&#xff08;也称为Wilcoxon秩和检验&#xff09;用于比较两个独立样本的中位数是否存在显著差异。它是一种非参数检验&#xff0c;适用于数据不满足正态分布假设的情况。 1、独立样本 # 创建两个独立样本数据…

250214-java类集框架

单列集合是list和set&#xff0c;list的实现类有ArrayList和LinkedList&#xff0c;前者是数组实现&#xff0c;后者是链表实现。list和set&#xff0c;前者有序、可重复&#xff0c;后者无序不可重复。 1.单列集合 1.1. list java.util.List接口继承自Collection接口&#…

ROS2 话题通信

1. 基本概念 发布-订阅模型&#xff1a;节点间通过话题&#xff08;Topic&#xff09;异步通信&#xff0c;发布者&#xff08;Publisher&#xff09;发送消息&#xff0c;订阅者&#xff08;Subscriber&#xff09;接收消息。 话题&#xff08;Topic&#xff09;&#xff1a;…

【学习资源】时间序列数据分析方法(1)

时间序列数据分析是一个有趣的话题&#xff0c;让我们多花一些时间来研究。此篇为第一篇文章。主要介绍特征提取方法、深度学习时序数据分析模型、参考资源。期望能帮助大家解决工业领域的相关问题。 1 特征提取方法&#xff1a;信号处理 (来源:INTELLIGENT FAULT DIAGNOSIS A…

Streamlit与Qlib:量化投资策略可视化实战

Streamlit与Qlib&#xff1a;量化投资策略可视化实战 1. 项目背景 在量化投资领域&#xff0c;数据可视化是理解和展示投资策略的关键。本文将详细介绍如何使用Streamlit和Qlib构建一个交互式的量化投资策略可视化应用。 2. 环境准备 2.1 安装依赖 # 安装必要的库 pip ins…

Ceph集群搭建2025(squid版)

squid版本维护年限 apt install -y cephadmecho >> "deb http://mirrors.163.com/ceph/debian-squid/ bookworm main" echo >> "deb-src http://mirrors.163.com/ceph/debian-squid/ bookworm main"#安装源 cephadm install #开始初始化一个最…

机器学习实战(3):线性回归——预测连续变量

第3集&#xff1a;线性回归——预测连续变量 在机器学习的世界中&#xff0c;线性回归是最基础、最直观的算法之一。它用于解决回归问题&#xff0c;即预测连续变量&#xff08;如房价、销售额等&#xff09;。尽管简单&#xff0c;但线性回归却是许多复杂模型的基石。今天我们…

【漫话机器学习系列】093.代价函数和损失函数(Cost and Loss Functions)

代价函数和损失函数&#xff08;Cost and Loss Functions&#xff09;详解 1. 引言 在机器学习和深度学习领域&#xff0c;代价函数&#xff08;Cost Function&#xff09;和损失函数&#xff08;Loss Function&#xff09;是核心概念&#xff0c;它们决定了模型的优化方向。…

LabVIEW 天然气水合物电声联合探测

天然气水合物被认为是潜在的清洁能源&#xff0c;其储量丰富&#xff0c;预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性&#xff0c;天然气水合物的探测面临诸多挑战&#xff0c;涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…

【记忆化搜索】最长递增子序列

文章目录 300. 最长递增子序列解题思路&#xff1a;递归 -> 记忆化搜索 300. 最长递增子序列 300. 最长递增子序列 ​ 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 ​ 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&am…