作者 | 波哥
审校 | 重楼
Java虚拟机(JVM)的自动内存管理是Java开发者的福音,它通过垃圾收集(GC)机制自动回收不再使用的对象,极大地简化了内存管理。然而,不恰当的GC配置或不理想的垃圾收集器选择可能会对应用性能产生负面影响。为了优化Java应用的性能,深入理解GC的原理和策略是至关重要的。本文笔者将详细探讨JVM的垃圾收集机制,包括内存模型、GC算法、各种垃圾收集器的特点及其调优策略。
JVM的内存模型是理解GC机制的基础。JVM将内存分为多个区域,主要包括堆(Heap)、方法区(Method Area)、程序计数器(Program Counter Register)、虚拟机栈(VM Stack)和本地方法栈(Native Method Stack)。
堆内存是Java虚拟机(JVM)管理的最大一块内存区域,它被所有线程共享,主要用于存放对象实例和数组。从垃圾收集的角度,堆内存进一步细分为新生代(Young Generation)、老年代(Old Generation)以及元空间(Metaspace,在Java 8之后取代了永久代PermGen)。
(1)新生代(Young Generation)
新生代是大多数新创建的对象的诞生地。由于对象的生存周期大多数较短,新生代的垃圾收集(Minor GC)发生频繁但速度快。新生代进一步分为三个区域:
(2)老年代(Old Generation)
随着时间的推移,一些在新生代中经历了多次GC依然存活的对象会被移动到老年代。老年代用于存放应用中生命周期长的对象。相较于新生代,老年代的空间更大,GC发生的频率更低,但每次GC的时间更长。
对象进入老年代(Old Generation)通常是基于它们的存活周期。JVM采用分代垃圾收集策略,其中对象首先在新生代(Young Generation)分配。随着垃圾收集的进行,只有存活下来的对象才会逐步晋升到老年代。具体而言,有几种情况下对象会进入到老年代:
(3)经历多次Minor GC后仍然存活的对象
新生代中的对象在经历了一定数量的Minor GC(垃圾收集只针对新生代的收集称为Minor GC)后,如果仍然存活,它们会被移动到老年代。JVM中有一个年龄计数器,每当对象在Minor GC后仍然存活,它的年龄就会增加。当对象的年龄增加到一定阈值(默认为15,但可以通过JVM参数-XX:MaxTenuringThreshold进行调整)时,这个对象就会被晋升到老年代。
(4)大对象直接分配到老年代
所谓的大对象是指需要大量连续内存空间的Java对象,例如那些很大的数组和长字符串。如果新生代中的Eden区无法容纳一个新创建的对象,JVM就会直接将这个对象分配到老年代。这样做是为了避免在新生代中为大对象分配内存后,进行Minor GC时发生大量的内存复制操作(因为新生代使用的是复制算法)。通过JVM参数-XX:PretenureSizeThreshold可以设置大对象的大小阈值。
(5)动态年龄判断
在新生代的两个Survivor区之间,对象每经过一次Minor GC就会年龄增加。如果在Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到-XX:MaxTenuringThreshold设置的年龄。
(6)空间分配担保
在进行Minor GC前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果这个条件不能满足,虚拟机会提前将新生代中的部分对象转移到老年代中,这个过程称为“空间分配担保”。目的是确保Minor GC可以顺利完成,不会因为老年代空间不足而触发更耗时的Full GC。
(7)元空间(Metaspace)
元空间用于存放类的元数据信息,如类的定义信息、常量、静态变量等,并使用本地内存(而非JVM堆内存)。在Java 8之前,这部分数据被存放在永久代中。元空间的引入是为了避免永久代容易发生的内存溢出问题,并提供更灵活的内存管理。
方法区(Method Area)是堆的一部分,也被称为非堆(Non-Heap),它被所有线程共享。方法区主要用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在Java 8及之后的版本中,传统的永久代(PermGen)被元空间(Metaspace)所取代。与永久代不同,元空间并不在虚拟机内存中,而是使用本地内存,因此,元空间的大小只受本地内存限制。
方法区的特点
方法区的垃圾收集
方法区的垃圾收集比较少见且难以执行,主要涉及两部分工作:废弃常量的回收和无用类的卸载。无用类的卸载条件相对严格,需要同时满足以下三个条件:
GC算法是实现垃圾收集的具体方法。主要的GC算法包括标记-清除(Mark-Sweep)、复制(Copying)和标记-整理(Mark-Compact),下面笔者将详细介绍这三种算法的工作原理以及它们的优缺点。
(1)工作原理
(2)优点
(3)缺点
(1)工作原理
(2)优点
(3)缺点
(1)工作原理
(2)优点
(3)缺点
现代JVM实现通常采用以上基本GC算法的变体或组合,以达到更高的垃圾收集效率和更低的停顿时间。例如:G1收集器就是将堆划分为多个区域(Region),并根据每个区域的垃圾回收价值进行增量收集,旨在平衡吞吐量和停顿时间。ZGC和Shenandoah收集器则采用了基于Region的复制算法,实现了几乎全程并发的垃圾收集,极大地减少了停顿时间。
JVM提供了多种垃圾收集器,下面我们大概介绍下目前主流的几种垃圾回收器及每种收集器的适用场景。
以上我们详细介绍了垃圾回收算法和主流的垃圾回收器,接下来我们详细介绍下在实际应用中,该如何根据具体应用特性进行调优。以下是一些调优的通用策略:
JVM提供了丰富的GC相关参数,通过调整这些参数(如新生代与老年代的比例、触发Full GC的阈值等),可以微调垃圾收集的行为,优化性能。
深入理解JVM的垃圾收集机制和各种垃圾收集器的特点是进行有效性能调优的前提。通过选择合适的垃圾收集器并适当调优,可以显著提升Java应用的性能,满足不同场景下对响应时间和吞吐量的需求。记住,没有一劳永逸的解决方案,性能优化是一个持续的过程,需要不断地监控、评估和调整。
波哥,互联行业从业10余年,先后担任项目总监及架构师。目前专攻技术,喜欢研究技术原理。技术全面,主攻Java,精通JVM底层机制及Spring全家桶底层框架原理,熟练掌握当前主流的中间件、服务网格等技术原理。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-76544-0.html垃圾收集器的秘密:深入理解JVM性能调优
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 实现一个刷数任务,需要思考哪些维度?