JVM和JVM内存管理

本文详细介绍了Java虚拟机(JVM)中的内存管理,特别是垃圾回收机制,包括内存区域划分、新生代和老年代的垃圾回收过程,以及方法区的管理。重点强调了内存管理在性能和稳定性上的关键作用,以及不同垃圾收集器的工作原理。
摘要由CSDN通过智能技术生成

     Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。Java源代码经过编译器编译后生成与平台无关的字节码文件(.class文件)。当Java程序需要在某个平台上运行时,JVM负责加载这些字节码文件,并解释或即时编译成对应平台的机器指令执行。JVM使java程序执行具有了“平台无关性”的特点,使java程序实现了“一次编译,到处运行”。

      JVM为java程序提供了内存管理,垃圾回收等服务,并负责安全管理,类加载,字节码验证,以及各种运行时环境的管理的优化工作。JVM对java程序的性能和安全性起着至关重要的作用。

一、内存管理很重要

      在这篇文章中,我们将主要回顾JVM中的内存管理和垃圾回收机制。因为程序在执行过程中可能会消耗大量的内存资源,特别是在处理大量数据、运行复杂算法或维持大量并发操作时。现代应用程序的性能和响应速度往往与内存管理密切相关:

        1. 资源有限:

        无论是服务器还是个人计算机,其物理内存资源都是有限的。如果程序对内存的使用没有有效控制,很容易导致系统内存耗尽,从而引发系统性能急剧下降甚至崩溃。例如,当内存占用过高时,操作系统可能开始频繁地进行页面交换(虚拟内存到硬盘),这会显著降低程序运行速度。

        2. 效率问题:

        良好的内存管理能够确保程序高效利用内存,避免不必要的内存分配和释放,减少内存碎片,提升内存利用率。内存碎片过多会导致可用连续内存空间变小,影响大对象的分配,进一步降低程序运行效率。

        3. 并发安全:

        在多线程环境中,不同的线程共享同一块内存区域,如果没有适当的同步机制和内存管理策略,可能会出现竞态条件和数据不一致的问题,影响程序正确性。

        4. 防止内存泄漏:

        程序中未释放不再使用的内存(即内存泄漏)会持续消耗系统资源,如果不加以管理,长时间运行的程序最终可能导致系统资源枯竭。

        5. 垃圾回收机制的重要性:

        对于像Java这样的语言,其JVM通过垃圾回收机制自动管理内存,它能跟踪并回收不再被引用的对象所占用的内存,大大降低了程序员手动管理内存的复杂度,同时也保证了内存的有效利用。

        因此,内存管理是软件工程中的一个核心环节,不仅关乎着程序能否稳定运行,还直接影响着系统的整体性能和用户体验。

二、JVM如何管理内存?

        JVM在进行内存管理时,主要针对Java堆(Heap)和方法区(Metaspace或Permanent Generation,在JDK 8及更高版本中为Metaspace)以及线程私有的内存区域,如程序计数器、虚拟机栈和本地方法栈进行管理。

1、java堆

        java堆,是java程序中所有线程共享的内存区域,存放java对象实例。堆被分为新生代,老年代,新生代又被分为Eden区,Survivor区(from 和 to)。jvm对每个区域使用不同的垃圾回收算法进行管理。因为新生代对象“朝生夕死”的特点,所以新生代使用复制算法。老年代的对象存活时间较长或者是被多次回收后存活下来的对象,使用标记清除算法或整理算法。

新生代垃圾回收过程:

     1、对象分配:新创建的对象会先被分配到Eden区;

     2、触发MinorGC:Eden区满时(或Eden区没有足够的空间分配给新创建的对象时) 触发MinorGC;

     3、存活对象复制:GC开始时,会扫码Eden区和上次GC仍存活并存在于Survivor区的对象(通常是from)复制到另一个空闲的Survivor的 to区);

     4、晋升老年代:当某个对象经历了多次新生代回收仍然存活(次数可配,通过 JVM 参数 -XX:MaxTenuringThreshold 设置),对象将被移动到老年代。

     5、清除空间:完成复制过程,GC会清除Eden区和Survivor的from区,然后交换两个Survivor区的角色,即原来的to 成为下一次MinorGC 的from区。

     6、调整大小:每个Survivor区的空间大小通常比Eden区小,例如可能是其1/3或者更小的比例,这样可以在有限的内存空间内高效地处理大部分短期生存的对象。

对象什么时候进入老年代?

      1、某个对象经历了多次新生代回收仍然存活。如上4;

      2、大对象直接分配。当对象所占空间超过配置(可通过jvm参数-XX:PretenureSizeThreshold设置),为了避免大对象在新生代多次复制降低效率,大对象将直接被分配到老年代;

      3、动态年龄判断。当Survivor空间不足矣容纳所有应该被保留的对象时,那些“年龄大”的对象即使没有达到配置次数,也会提前进入老年代,以给年轻代腾出空间。

      4、分配担保机制失败。分配担保机制是jvm在进行垃圾回收时的一种策略,用于处理可能出现的内存空间不足的问题。

      在Minor GC前,jvm会判断Survivor区和老年代剩余连续空间是否能容纳从Eden区和from Survivor 区复制过来的存活对象。如果不能容纳,这时候要查看 -XX:HandlePromotionFailure 参数(默认值通常为 true),是否允许担保失败。

      如果允许担保失败,就会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC(因为是以历次晋升对象的平均大小为参考,所以这次可能会失败,是有风险的)。如果不允许,或者老年代最大可用连续空间小于历次晋升到老年代对象的平均大小,那么将尽可能将年轻代存活的年龄大的对象或者大对象提前晋升到老年代(即使未达到-XX:MaxTenuringThreshold 或-XX:PretenureSizeThreshold阈值设置).这样能减少Minor GC过程中处理对象的数量,从而降低GC压力,并尽量保证有足够空间存放从新生代晋升过来的对象,进而降低Full GC发生的概率。

老年代的垃圾回收过程:

     老年代垃圾回收的过程通常涉及更复杂的算法和策略,因为它处理的是长期存活的对象。下面以几个常见的老年代垃圾收集器为例简述其工作过程

     CMS(Concurrent Mark Sweep)垃圾收集器

     CMS是一个主要用于老年代的并发标记清除垃圾收集器,其工作流程包含以下四个阶段:

1. 初始标记(Initial Mark):此阶段会暂停所有应用线程,仅标记从GC Roots直接可达的老年代对象以及新生代晋升到老年代的对象。

2. 并发标记(Concurrent Mark):在不暂停应用线程的情况下进行,遍历整个堆并标记所有可到达的老年代对象。由于应用程序持续运行,所以这一步可能会丢失部分垃圾对象的标记。

3. 重新标记(Remark):再次暂停应用线程,修正并发标记阶段因程序继续运行而可能导致的标记错误。

4. 并发清除(Concurrent Sweep):在不暂停应用线程的前提下,清除已被标记为不可达的老年代对象,并且可能进行碎片整理操作,但CMS本身不会做完整的碎片整理,导致空间利用率不高

       G1(Garbage-First)垃圾收集器

       G1垃圾收集器设计用于大规模内存管理它将整个堆划分为多个大小相等的区域(Region),每个区域都可以作为Eden、Survivor或老年代的一部分。G1在回收老年代时采用Stop-the-world模式执行,但尽量减小STW的时间:

1. 并发标记(Initial Mark, Concurrent Mark):与CMS类似,G1首先标记出GC Roots可达的所有对象,然后并发地进行全局标记。

2. 最终标记(Remark):解决并发标记期间因应用线程并发运行导致的引用变动问题,完成精确的标记。

3. 清理(Cleanup):删除已标记为垃圾的对象引用,并统计各个Region中的存活对象分布情况,为后续的混合收集(Mixed GC)做准备。

4. 混合收集(Mixed GC):G1会同时回收年轻代和部分选定的老年代区域,这些区域是根据垃圾回收收益最大化的原则选择的。在回收过程中也会尝试进行压缩和整理,减少内存碎片

      Parallel Old 或 Serial Old 垃圾收集器
     这两种收集器是基于传统的“标记-压缩”算法(Mark-Sweep-Compact)来处理老年代垃圾回收:

     1. 标记:同样需要暂停应用线程,标记出所有存活对象。
     2. 压缩:清除掉所有未被标记的对象,并将剩余存活对象移动到一起,消除内存碎片。
   
       在这些过程中,JVM都会尽可能优化STW(Stop-The-World)停顿时间,确保应用的响应性能。对于老年代而言,由于对象生命周期较长且数量相对较多,因此垃圾回收的成本较高,对性能的影响也较大。不同的收集器通过各种并发和增量的技术来改善这一问题。

2、方法区(元空间 Metaspace)

     方法区在java 8 和更高的版本被元空间替代,用于存储类的元数据信息,如类,方法,常量池,字段描述符等。“元”与开始联系,有“根本”,“本来”的意思。元空间不在堆上分配空间,不归堆内存管理,而是直接在本地操作系统内存上管理,所以其扩展方法和管理方式和堆不同。

     JVM会动态调整Metaspace的大小,当加载的类信息过多导致Metaspace不足时,JVM会触发类卸载操作,并在必要时扩展Metaspace的大小,直至达到最大值(可以通过 -XX:MaxMetaspaceSize 设置)。如果Metaspace达到其最大容量且无法扩展,则会导致 java.lang.OutOfMemoryError: Metaspace 错误。     

    所以,在实际应用中,随着我们工程的扩展和需求迭代,类的元数据信息增加或减少,动态调整 -XX:MaxMetaspaceSize 是十分有必要的,可以有效的帮助我们合理的利用内存空间,并防止内存溢出。并且,因为Java程序在运行过程中,可能动态生成类(如jsp页面转换成Java类,cglib动态代理生成类),加载第三方类库、应用过程中通过反射或classLoader加载额外的类等,我们更需要结合监控预警等手段来监测元空间是否足够。同时,持续优化代码以减少不必要的类加载和卸载也是降低Metaspace占用的有效手段

3、线程私有的内存区域

     其实在之前的文章​​​​​​​JVM-运行时数据区-CSDN博客介绍过线程私有的内存区域,这部分区域与堆的管理方式不同。线程私有的内存区域,通过预设栈大小和以及线程的生命周期栈帧的压栈和出栈自然管理。

     程序计数器:每个线程都有,用于记录当前线程执行的字节码地址,不需特别的内存管理策略;

     虚拟机栈:每个方法调用都会创建一个栈帧,这些栈帧构成了虚拟机栈。栈的空间大小有限,可以通过-Xss参数设置每个线程的栈大小。当线程请求的栈深度超过允许深度的最大值时,将抛出:StackOverFlowError;而如果扩大栈空间不能被满足时,将可能抛出:OutOfMemoryError: Java heap space

     因此,在实际应用中,要注意方法的调用深度!合理的方法深度,不仅关乎代码的可读性,更会影响系统的可用性和可维护性。

   本地方法栈Java本地方法/Java native方法/JNI_java里的本地库方法-CSDN博客 用于对本地方法的调用,如调用非Java语言的方法或者调用操作系统或硬件的方法,也会像虚拟机栈一样存在 StackOverFlowError和OutOfMemoryError异常,只是不同的JVM对JNI执行的管理不同。

三、总结:

     以上是从内存管理的角度对jvm进行的知识框架梳理,以后在工作中还要多多使用,多多体会。同时,结合已有的工作经验,我更加深刻的认识到,有些时候因为对原理的不了解,研发过程中的一些不经意的"小操作",都可能引起巨大风暴:大量没有被引用的类文件,如果被加载到jvm中,在metaSpaceSize设置的少时,有可能引起OutofMemoryError:Metaspace错误;队列长度设置不当,很有可能导致内存溢出;完善的内存监控系统可以使我们的系统如虎添翼。


关于jvm不同区域的回收机制:

1. Minor GC(年轻代垃圾回收):仅针对新生代(Young Generation)进行的垃圾回收。新生代中的对象大部分是生命周期较短的对象,当新生代空间不足时,就会触发Minor GC。

2. Major GC或者称为 Old GC(老年代垃圾回收):主要针对老年代(Old Generation)进行的垃圾回收,通常在老年代空间不足或发生晋升失败时发生。虽然术语上不如Minor GC常用,但实际上在某些场景下,比如CMS和G1等垃圾收集器中,对老年代的单独回收是可以独立于整个堆的回收存在的。

3.Full GC(全局垃圾回收):是对整个Java堆以及方法区(在Java 8及之前版本为永久代,在Java 9之后为元空间Metaspace)进行全面的垃圾回收。这不仅包括新生代和老年代,还包括其他可能需要回收的内存区域。Full GC的发生频率相对较低,但停顿时间较长,因为它涉及了整个堆的清理和压缩操作。

除此之外,还有特定垃圾收集器特有的回收行为:
Mixed GC(混合垃圾回收):这是G1垃圾收集器特有的回收方式,它会同时回收部分新生代和一部分选定的老年代分区。
Concurrent Mode Failure (CMF):在CMS垃圾收集器中,由于种种原因导致并发阶段无法完成任务而被迫切换到Serial Old模式进行GC,这种情况也会伴随着较大的STW停顿时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王师傅66

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值