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

JVM专题九:JVM分代知识点梳理

今天开始,咱们开始剖析JVM内存划分的原理细节,以及我们创建的那些对象在JVM中到底是如何分配,如何流动的,首先解决第一个问题:JVM内存的一个分代模型:年轻代、老年代、永久通过之前的专题我们知道,那就是我们在代码里创建的对象,“都”会进入到Java堆内存。

为什么需要分代

JVM需要分代的主要原因是优化垃圾回收(Garbage Collection, GC)的效率。以下是分代垃圾回收模型的几个关键优势:

  1. 对象生命周期的统计特性: 大多数对象都是朝生夕死,即它们被创建后很快就会变得不再被使用。分代模型利用了这一特性,将新创建的对象放在年轻代,这样大部分短暂的对象可以快速被回收,而不需要频繁地对整个堆进行垃圾回收。

  2. 垃圾回收效率: 年轻代的垃圾回收(Minor GC)通常比老年代的垃圾回收(Major GC或Full GC)要快得多,因为年轻代的对象数量多,但存活率低。通过在年轻代进行频繁的垃圾回收,可以快速清理大量不再使用的对象,减少对老年代的影响。

  3. 减少Full GC的频率: 由于大部分对象在年轻代就会被回收,因此进入老年代的对象相对较少,这减少了对整个堆进行垃圾回收(Full GC)的需要,Full GC通常成本更高,因为它需要扫描整个堆内存。

  4. 内存分配的局部性优化: 年轻代的内存分配通常具有很好的局部性,因为新对象倾向于在相近的内存地址上创建。这有助于现代CPU缓存的利用,从而提高内存访问效率。

  5. 对象老化的自然过程: 对象在年轻代中经过多次GC后,如果仍然存活,说明它们可能是长时间存活的对象。将这些对象移动到老年代可以减少年轻代中对象的老化检查,从而提高GC效率。

  6. 不同的垃圾回收算法: 年轻代和老年代可以使用不同的垃圾回收算法。例如,年轻代通常使用复制算法,因为它简单且高效,而老年代可能使用标记-清除或标记-清除-整理算法,因为老年代的对象存活率更高,需要更复杂的算法来处理碎片问题。

  7. 内存空间的合理分配: 通过分代,JVM可以根据对象的生命周期特性合理分配内存空间。年轻代通常分配较小的内存空间,因为大多数对象都是短暂存在的,而老年代则分配较大的空间,用于存放长期存活的对象。

  8. 适应不同的应用场景: 不同的应用可能有不同的对象生命周期特性。通过分代,JVM可以更灵活地适应不同的应用场景,通过调整年轻代和老年代的大小,以及垃圾回收算法,来优化性能。

通过上述分代模型的优势,JVM能够更高效地管理内存,减少垃圾回收的开销,从而提高Java程序的整体性能。

新生代是如何进入老年代的?

新生代对象进入老年代的过程,通常被称为"晋升"(Promotion)。以下是对象从新生代晋升到老年代的几个关键步骤和机制:

  1. 对象分配: 当一个新对象在Java程序中创建时,它首先被分配到新生代的Eden区。

  2. Minor GC: 新生代会定期进行Minor GC,以回收Eden区和Survivor区中不再被引用的对象。存活的对象会被复制到另一个Survivor区(S0或S1)。

  3. 对象年龄: 在Minor GC之后,存活的对象会有一个年龄计数器,每次在Survivor区之间复制时,对象的年龄会增加。

  4. 年龄阈值: 对象的年龄达到一定的阈值(默认是15,但可以通过-XX:MaxTenuringThreshold参数调整),对象就会被认为足够"老",应该被晋升到老年代。

  5. 动态年龄调整: JVM会根据Survivor区的使用情况动态调整对象晋升到老年代的年龄阈值。如果Survivor区空间不足,JVM可能会降低年龄阈值,使对象更快地晋升到老年代。

  6. 空间分配担保: 当一个对象在Survivor区存活多次后,JVM会检查老年代的可用空间。如果老年代有足够的空间,对象会被晋升;如果没有,JVM会尝试进行一次Full GC来清理老年代的空间,然后再尝试晋升对象。

  7. 晋升到老年代: 一旦对象的年龄达到阈值,或者Survivor区空间不足,对象就会被复制到老年代。在老年代,对象不需要像在新生代那样频繁移动,因为老年代的垃圾回收频率较低。

  8. 空间分配担保的实现: 晋升过程中,JVM会使用一种称为"空间分配担保"(Space Accounting Guarantee)的策略,确保老年代有足够的空间来接收新生代晋升的对象。如果老年代空间不足,JVM会尝试压缩老年代,或者触发Full GC。

  9. TLABs: 为了进一步优化内存分配,JVM还使用线程本地分配缓冲区(Thread Local Allocation Buffers, TLABs)。每个线程有自己的TLAB,用于分配对象,这样可以减少线程之间的内存分配竞争。

通过这些机制,JVM能够根据对象的生命周期特性,有效地将对象从新生代晋升到老年代,同时保持垃圾回收的效率和性能。

什么时候触发新生代垃圾回收

新生代垃圾回收(Minor GC)的触发时机如下:

  1. 内存分配限制:当新生代的Eden区内存耗尽,无法满足新对象的内存分配请求时,将触发Minor GC以释放内存空间。

  2. 内存使用监控:JVM内部监控新生代内存使用情况,当达到预设的内存使用阈值时,可能触发Minor GC。

  3. GC算法实现:根据JVM所采用的垃圾回收算法实现,可能存在特定的条件或时间间隔来触发Minor GC。

  4. 显式GC请求:尽管System.gc()方法的调用并不保证立即执行GC,但它提供了一个显式的GC请求,JVM可能会响应此请求并触发Minor GC。

  5. 自适应调整:JVM可能采用自适应算法根据应用程序的内存分配模式和垃圾回收效率来动态决定Minor GC的触发时机。

  6. 内存分配速率:如果对象的内存分配速率持续超过GC回收速率,JVM可能会触发Minor GC以避免内存溢出。

  7. 堆内存压力:在堆内存使用接近其最大容量时,JVM可能会增加Minor GC的频率,以防止内存耗尽。

  8. JVM参数配置:特定的JVM启动参数,如-XX:+UseAdaptiveSizePolicy,允许JVM根据当前的内存分配和回收效率自适应地调整新生代的大小和Minor GC的触发策略。

  9. 外部系统因素:操作系统的内存压力或资源限制也可能间接影响JVM对Minor GC的触发决策。

  10. GC日志和性能监控:通过监控GC日志和性能指标,JVM可以分析对象生命周期和内存分配模式,以优化Minor GC的触发时机。

Minor GC是JVM内存管理的关键机制之一,其触发机制的设计旨在平衡内存回收的效率和应用程序的性能需求。

什么情况会进入老年代

在JVM中,对象从新生代晋升到老年代的条件主要包括以下几点:

  1. 年龄阈值:对象在Survivor区存活的次数达到JVM设置的年龄阈值(-XX:MaxTenuringThreshold参数,默认为15),该对象将被晋升到老年代。

  2. 空间分配担保:如果Survivor区的空间不足以容纳经过Minor GC后存活的对象,JVM会检查老年代的可用空间。如果老年代有足够的空间,这些对象将被直接晋升到老年代。

  3. 动态年龄调整:JVM可能会根据当前内存分配和回收的统计信息,动态调整对象晋升到老年代的年龄阈值。

  4. 老年代空间充足:即使对象的年龄未达到最大年龄阈值,如果老年代有足够的空间,JVM也可能将对象提前晋升到老年代。

  5. Full GC后的存活对象:在执行Full GC(老年代GC)后,新生代中所有存活的对象可能被直接晋升到老年代,以减少跨代引用的问题。

  6. 大对象直接分配:对于占用大量连续内存的大对象(如大型数组),JVM可能会直接将其分配到老年代,以避免在新生代中分配时产生过多的内存碎片。

  7. 长期存活对象:某些对象由于其生命周期较长,经过多次Minor GC后仍然存活,这些对象最终会被晋升到老年代。

  8. JVM参数配置:JVM参数如-XX:TargetSurvivorRatio(目标Survivor区使用率)和-XX:PretenureSizeThreshold(直接分配到老年代的对象大小阈值)可以影响对象晋升到老年代的行为。

  9. 内存分配策略:JVM的内存分配策略,如TLAB(Thread Local Allocation Buffer)的使用,也可能影响对象是否直接在老年代分配。

  10. 垃圾回收器的选择:不同的垃圾回收器可能有不同的晋升策略和内存管理机制。

对象晋升到老年代是一个动态的过程,JVM会根据当前的内存使用情况和垃圾回收效率来做出最合适的决策。

分别怎么进行垃圾回收

VM中的垃圾回收(Garbage Collection, GC)主要针对新生代和老年代采用不同的策略,以下是两种区域垃圾回收的基本方法:

新生代垃圾回收(Minor GC或Young GC)

  1. 复制算法:新生代通常使用复制算法(Copying)。该算法将Eden区和Survivor区(S0)中存活的对象复制到另一个Survivor区(S1)。复制完成后,Eden区和S0中的所有对象都会被清理。

  2. 对象年龄:每次Minor GC后,存活的对象年龄会递增。当对象的年龄达到-XX:MaxTenuringThreshold参数设定的阈值时,对象会被晋升到老年代。

  3. 清理过程:Minor GC主要涉及Eden区和Survivor区的清理,不涉及老年代。

  4. 内存分配:新创建的对象首先分配到Eden区,或者直接分配到TLAB(如果启用)。

  5. 触发条件:当Eden区被填满,或者Survivor区无法容纳从Eden区复制过来的存活对象时,会触发Minor GC。

老年代垃圾回收(Major GC或Full GC)

  1. 标记-清除算法:老年代的垃圾回收通常从标记所有存活的对象开始,然后清除未被标记的对象。

  2. 标记-清除-整理:为了防止内存碎片化,清除之后可能会进行一次整理,将存活的对象移动到堆的一端,以便于连续内存分配。

  3. 并发收集:老年代的垃圾回收可能采用并发收集算法,以减少GC暂停时间。

  4. 分代收集:老年代的GC可能与新生代的GC同时进行,这种GC被称为Full GC。

  5. 压缩技术:为了减少内存碎片,老年代的GC可能采用压缩技术,如G1 GC的Region重排。

  6. 触发条件:老年代GC可能由以下条件触发:

    • 老年代空间不足。
    • 显式调用System.gc()
    • 达到某些JVM参数设定的内存使用阈值。
  7. 内存分配:老年代用于存放长期存活的对象,以及从新生代晋升上来的对象。

  8. 性能影响:由于老年代GC涉及整个堆或大部分堆的内存,它通常比Minor GC有更长的暂停时间和更大的性能影响。

总结

  • 新生代GC(Minor GC)通常更频繁,使用复制算法,回收速度快,影响较小。
  • 老年代GC(Major GC或Full GC)较少见,使用标记-清除(可能伴随整理)算法,回收速度慢,可能引起较长时间的暂停。

JVM的垃圾回收策略和算法可以根据具体的垃圾回收器和JVM参数进行调整,以适应不同的应用场景和性能要求。

什么是永久代?

在JVM中,永久代(Permanent Generation,简称PermGen)是方法区的一种实现,主要用于存储类信息、常量、静态变量等数据。以下是对您提供内容的扩展和澄清:

永久代(PermGen)的角色

  • 类信息存储:永久代存储了类的元数据,包括类的字段、方法、构造函数等信息。
  • 常量池:存储编译期就已经确定的字面量和符号引用。
  • 静态变量:存储类的静态变量。
  • ClassLoader数据:存储类加载器的引用。

方法区垃圾回收的条件

方法区中的类信息并不是一直存在的,它们也可以被垃圾回收。以下是类信息可以被回收的条件:

  1. 实例对象的回收:类的所有实例对象已经被垃圾回收,即Java堆中不存在该类的任何实例。
  2. ClassLoader的回收:加载该类的ClassLoader已经被垃圾回收。
  3. 无引用的Class对象:堆中不存在任何对该Class对象的引用。

垃圾回收机制

  • 条件触发:只有当上述三个条件同时满足时,类信息才会变成垃圾,被JVM的垃圾回收机制处理。
  • 回收过程:JVM在进行垃圾回收时,会检查类是否满足回收条件,如果满足,就会在下一次的Full GC中被清除。

元空间

  • 元空间(Metaspace):在Java 8中,永久代被元空间所取代。元空间使用的是本地内存(Native Memory),而不是虚拟机内存(Heap Memory),这样做的好处是可以避免永久代空间不足的问题。

永久代是JVM中用于存储类信息的重要内存区域,但它并不是永久存储的。类信息在满足特定条件后可以被垃圾回收,这是JVM内存管理的一部分,有助于释放不再使用的类信息,优化内存使用。随着Java 8的发布,元空间取代了永久代,提供了更为灵活的类信息存储方式。

今天介绍了很多很多概念相关的知识点,让大家有个大概的印象,后续会一个一个剖析。

专题汇总

JVM专题一:深入分析Java工作机制

JVM专题二:Java如何进行编译的

JVM专题三:Java代码如何运行

JVM专题四:JVM的类加载机制

JVM专题五:类加载器与双亲委派机制

JVM专题六:JVM的内存模型

JVM专题七:JVM垃圾回收机制

JVM专题八:JVM如何判断可回收对象

JVM专题九:JVM分代知识点梳理

JVM专题十:JVM中的垃圾回收机制

JVM专题十一:JVM 中的收集器一

JVM专题十二:JVM 中的收集器二

JVM专题十三:总结与整理(面试常用)

相关文章:

  • mysql8 锁表与解锁
  • java:aocache:基于aspectJ实现的方法缓存工具
  • 等保2.0对云计算有哪些特定的安全要求?
  • AI Agent项目实战(03)-利用TTS技术让你的AI Agent发声
  • jenkins在使用pipeline时,为何没有方块形视图
  • CSF视频文件格式转换WMV格式(2024年可用)
  • k8s架构设计思想
  • python Flask methods
  • 【linux】网络基础(2)——udp协议
  • RabbitMQ 之 延迟队列
  • 【开发环境】MacBook M2安装git并拉取gitlab项目,解决gitlab出现Access Token使用无效的方法
  • ElementUI搭建
  • 百日筑基第八天-看看mybatis
  • ARP 原理详解 二
  • Linux——查找文件-find(详细)
  • 【Leetcode】104. 二叉树的最大深度
  • 08.Android之View事件问题
  • canvas 绘制双线技巧
  • fetch 从初识到应用
  • jquery ajax学习笔记
  • js写一个简单的选项卡
  • Koa2 之文件上传下载
  • Linux链接文件
  • Lsb图片隐写
  • Nacos系列:Nacos的Java SDK使用
  • Python利用正则抓取网页内容保存到本地
  • 阿里云购买磁盘后挂载
  • 百度小程序遇到的问题
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 仿天猫超市收藏抛物线动画工具库
  • 回流、重绘及其优化
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 积累各种好的链接
  • #stm32整理(一)flash读写
  • #单片机(TB6600驱动42步进电机)
  • ( 10 )MySQL中的外键
  • (12)Linux 常见的三种进程状态
  • (14)Hive调优——合并小文件
  • (Oracle)SQL优化技巧(一):分页查询
  • (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (力扣)1314.矩阵区域和
  • (三)Kafka 监控之 Streams 监控(Streams Monitoring)和其他
  • (十三)Flask之特殊装饰器详解
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)memcache、redis缓存
  • 、写入Shellcode到注册表上线
  • . Flume面试题
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET开发者必备的11款免费工具
  • /etc/shadow字段详解