热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

闲谈JVM(二):浅析新老生代参数配置

文章目录

文章目录

  • 前言
  • Heap区新老生代
    • 新生代参数配置
      • NewSize
      • MaxNewSize
      • Xmn
      • NewRatio
      • SurvivorRatio
      • 新生代的GC
    • 老生代参数配置
      • 对象何时进入老生代?
      • 老生代GC
  • 结语

前言

在上一篇中,我们介绍了JVM中Heap区的基本参数设置,可以参见:闲谈JVM(一):浅析JVM Heap参数配置

在Heap区中,又被划分为几个区域,分别为新生代与老生代,而我们知道,绝大多数的对象都是“朝生夕死”的,在新生代阶段就会被回收掉,由此可以看出,新生代是非常重要的一个区域,而经过多次回收后,仍存活的对象,将进入老生代,本篇,我们就来了解一下新生代与老生代相关的参数配置。

Heap区新老生代

在最开始,我们还是来看一下JVM的内存模型:

JVM内存模型

从上图中可以看到,Heap区由新生代与老生代组成,而新生代的比重也是比较大的,而进一步细分,新生代中又可以分为三个区域:Eden、Survivor1(From)、Survivor2(To)

新生代参数配置

新生代的大小配置,主要由几个参数控制:Xmn、NewSize、MaxNewSize、NewRatio、SurvivorRatio。

我们逐个来分说明参数的作用。

NewSize

NewSize是设置新生代有效内存的初始化大小,也可以说是新生代有效内存的最小值,当新生代回收之后有效内存可能会进行缩容,这个参数就指定了能缩小到的最小值。

该参数的使用方式如下:

-XX:NewSize=20m

在Linux环境下,JDK8中该参数的默认大小即为20M。

需要注意的是,该参数仅当Xms与Xmx不一致时,才会在JVM初始化时分配这么大的内存,当Xms与Xmx设置为同一个值时,该参数无效,初始化的新生代大小将会使用MaxNewSize配置的大小。

MaxNewSize

MaxNewSize顾名思义,就是设置新生代有效内存的最大值,当对新生代进行回收之后可能会对新生代的有效内存进行扩容,那到底能扩容到多大,这就是最大值。

NewSize与MaxNewSize是一组参数,分别对应新生代有效内存的最小值与最大值。

该参数的使用方式如下:

-XX:MaxNewSize=100m

在Linux环境下,JDK8中该参数的默认大小为318.5M。

Xmn

Xmn同样是设置新生代的大小,等同于同时设置了NewSize和MaxNewSize,并且值都相等,例如

-Xmn128m

等同于

-XX:NewSize=128m
-XX:MaxNewSize=128m

在绝大多数场景下,对象都是朝生暮死的,在新生代阶段就会被回收掉,在高并发的场景下,会高频创建大量的对象在新生代中,对于这种场景下,可以合理分析堆区内存的情况,适当的调大新生代的大小,避免新生代迅速堆满频繁触发YGC,也可以一定程度的避免对象快速进入老生代。

NewRatio

NewRatio参数表示当前老生代可用内存/当前新生代可用内存的比值,即Old/New,在JDK8中,默认是2,参数使用的方式如下:

-XX:NewRatio=2

我们来验证一下是否是这样的。

写一个简单的Demo:

public class HelloWorld {
public static void main(String[] args) {
try {
Thread.sleep(1000 * 60);
} catch(Exception e) {
System.out.println("Error");
}
System.out.println("hello world");
}
}

我们设置堆区的大小为30M:

NewRatio验证1
然后来看一下堆区的分配情况:
NewRatio验证1

可以看到,新生代与老生代的比值的确为2,不过这是当JVM初始化时,堆区的一个比例,当运行一段时间,发生GC之后,新生代与老生代的比例不一定遵守这个比例,而是进行动态计算的。

同时需要注意的是,如果设置了Xmn或者NewSize/MaxNewSize,JVM初始化时,那么NewRatio将会被覆盖,即不会生效。

我们来验证一下这个说法是否成立。
NewRatio验证2

启动测试代码,设置堆区大小为30M,新生代大小为20M,然后我们在来看一下内存分布的情况:
NewRatio验证3

可以看到,新生代大小为20M,老生代为10M,也就是说,NewRatio的默认值并未生效。

SurvivorRatio

新生代由Eden和两块Survivor组成,这两块Survivor通常一个叫做From Space,一个叫做To Space,并且两个大小一致,每次GC发生的时候,会将Eden和From Space里的可达对象往To Space里拷贝,或者晋升到Old。

GC完成之后正常情况下是Eden为空的,并且会对换下From Space和To Space的位置,对换完之后的To Space又为空了。

SurvivorRatio表示新生代中三个分代,Eden、Survivor1(From)、Survivor2(To)的比值

该参数的使用方式如下:

-XX:SurvivorRatio=8

在Linux环境下,JDK8中该参数的默认大小即为8,表示Eden:From:To的比值为8:1:1。

我们来验证一下:

还是启动Demo程序,然后查看:
SurvivorRatio

在看一下真正的内存分布情况:

我们设置JVM参数:

java -Xms30M -Xmx30M -Xmn12M -XX:SurvivorRatio=1 HelloWorld

这里我们将SurvivorRatio设置为1,即Eden:From:To的比值为1:1:1。

使用命令查看内存的实际分布情况:

jstat -gc PID 1000 3

输出结果:

S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
4096.0 4096.0 0.0 0.0 4096.0 819.9 18432.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000
4096.0 4096.0 0.0 0.0 4096.0 819.9 18432.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000
4096.0 4096.0 0.0 0.0 4096.0 819.9 18432.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000

其中几个关键的参数:

  • S0C survivor0大小
  • S1C survivor1大小
  • EC Eden区大小

我们可以看到,三个区域的大小均为4096K,即4M,验证了SurvivorRatio参数。

新生代的GC

新生代的GC垃圾回收器,主要有三种,这里简单说明一下:

1、Serial 收集器,它是一个单线程的收集器,但它的单线程的意义并不仅仅说明它只会是使用一个 CPU 或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

2、ParNew 收集器,ParNew 收集器其实就是 Serial 收集器的多线程版本。ParNew 最重要的一点,是唯一的可以与老生代的CMS 收集器配合使用的新生代收集器,可以通过参数-XX:+UseParNewGC进行指定。

3、Parallel Scavenge 收集器,它与其他收集器的不同之处在于:它的关注点与其他收集器不同。CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量( Throughput)。

在JDK8中,生产环境中较为常见的组合是新生代的 ParNew GC + 老生代的CMS GC,这套组合也是比较推荐的。

老生代参数配置

上面我们了解完了Heap区中新生代的主要参数配置,那么下面聊一下老生代的配置,关于老生代的参数配置,并不多,老生代的大小 = 堆区大小 - 新生代大小,我们依旧是通过实际情况,验证我们的说法。

启动测试程序,设置堆区大小30M,新生代大小10M,验证老生代大小是否为20M:

java -Xms30M -Xmx30M -Xmn10M HelloWorld

打印内存分布:

S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
1024.0 1024.0 0.0 0.0 8192.0 820.1 20480.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000
1024.0 1024.0 0.0 0.0 8192.0 820.1 20480.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000
1024.0 1024.0 0.0 0.0 8192.0 820.1 20480.0 0.0 2240.0 1.2 0.0 0.0 0 0.000 0 0.000 0.000

其中OC一项,为老生代当前大小,为20480K,即20M,验证了我们的说法。

对象何时进入老生代?

我们都知道,对象刚被创建时,一般情况下是会被创建在新生代的,只有超过指定阈值的大对象,才会被直接创建在老生代当中,大多数的对象的生命周期仅存在于新生代,会在新生代阶段就被回收掉了,但是仍有部分对象会进入到老生代中区,那么,新生代的对象何时会进入老生代?

进入到老生代的时机,可以通过参数进行控制:

-XX:MaxTenuringThreshold=15

Each object in Java heap has a header which is used by Garbage Collection (GC) algorithm. The young space collector (which is responsible for object promotion) uses a few bit(s) from this header to track the number of collections object that have survived (32-bit JVM use 4 bits for this, 64-bit probably some more).

对象从新生代晋升到老年代的年龄阈值(每次 Young GC 留下来的对象年龄加一),默认值15,表示对象要经过15次 GC 才能从新生代晋升到老年代。

但是在老生代的CMS GC下,该默认值为6,我们来验证一下,设置GC算法为CMS:

java -Xms30M -Xmx30M -Xmn10M -XX:+UseConcMarkSweepGC HelloWorld

通过jinfo查看:

C:\Users\sheqian.xgy>jinfo -flag MaxTenuringThreshold 12768
-XX:MaxTenuringThreshold=6

可以看到,CMS GC下,晋升的次数默认为6。

不过需要注意的是,当设置了这个值得时候,第一次会以它为准,而在运行阶段,该阈值是动态调整,不过不会超过这个值。

老生代GC

老生代的垃圾收集器,主要分为四种,分别如下:

1、Serial Old 收集器,Serial Old 是 Serial 收集器的老年代版本,它同样是一个单线程收集器,使用 “标记-整理” 算法。一般情况下,不会使用。

2、Parallel old 收集器,Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记-整理” 算法。

3、CMS 收集器,以获取最短回收停顿时间为目标,目前较为推荐的GC 收集器,多数应用于互联网站或者B/S系统的服务器端上。

4、G1 收集器,Java 9以后的默认收集器,当前最炙手可热的GC 收集器,可以说兼顾了性能与时间的GC 收集器。

在Java8中,默认的GC收集器采用了Parallel GC,也可以通过参数-XX:+UseParallelGC进行指定,来看一下Oracle官方的说法:

The parallel collector (also referred to here as the throughput collector) is a generational collector similar to the serial collector; the primary difference is that multiple threads are used to speed up garbage collection. The parallel collector is enabled with the command-line option -XX:+UseParallelGC. By default, with this option, both minor and major collections are executed in parallel to further reduce garbage collection overhead.

The parallel collector is selected by default on server-class machines. In addition, the parallel collector uses a method of automatic tuning that allows you to specify specific behaviors instead of generation sizes and other low-level tuning details. You can specify maximum garbage collection pause time, throughput, and footprint (heap size).

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html

关于GC 收集器的内容,本篇先不做过多详细的介绍,在后面的篇幅中,会着重对比各个GC 收集器。

结语

本篇,我们介绍了Heap区中新生代与老生代的参数配置,了解了新老生代较为常用的参数配置,其中新生代的参数控制是较为重要的,因为大多数对象在这个阶段就会被回收掉,新生代的大小如果设置过大,会增大YGC的压力,设置过小则会频繁堆满,触发YGC,因此需要根据线上的业务实际情况,酌情调整。

下一篇,我们会对Java8中新增的本地元空间(metaSpace)的参数配置进行介绍,敬请期待。

本篇参考:

JVM 学习——垃圾收集器与内存分配策略:
http://matt33.com/2016/09/18/jvm-basic2/#Parallel-Scavenge-%E6%94%B6%E9%9B%86%E5%99%A8

Oralce官方文档 Parallel Collector:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html

JVM监控和调优常用命令工具总结
https://www.cnblogs.com/wxisme/p/9878494.html


推荐阅读
  • 深入浅析JVM垃圾回收机制与收集器概述
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》的阅读心得进行整理,详细探讨了JVM的垃圾回收机制及其各类收集器的特点与应用场景。通过分析不同垃圾收集器的工作原理和性能表现,帮助读者深入了解JVM内存管理的核心技术,为优化Java应用程序提供实用指导。 ... [详细]
  • 2021年7月22日上午9点至中午12点,我专注于Java的学习,重点补充了之前在视频中遗漏的多线程知识。首先,我了解了进程的概念,即程序在内存中运行时形成的一个独立执行单元。其次,学习了线程作为进程的组成部分,是进程中可并发执行的最小单位,负责处理具体的任务。此外,我还深入研究了Runnable接口的使用方法及其在多线程编程中的重要作用。 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • Python全局解释器锁(GIL)机制详解
    在Python中,线程是操作系统级别的原生线程。为了确保多线程环境下的内存安全,Python虚拟机引入了全局解释器锁(Global Interpreter Lock,简称GIL)。GIL是一种互斥锁,用于保护对解释器状态的访问,防止多个线程同时执行字节码。尽管GIL有助于简化内存管理,但它也限制了多核处理器上多线程程序的并行性能。本文将深入探讨GIL的工作原理及其对Python多线程编程的影响。 ... [详细]
  • 并发编程入门:初探多任务处理技术
    并发编程入门:探索多任务处理技术并发编程是指在单个处理器上高效地管理多个任务的执行过程。其核心在于通过合理分配和协调任务,提高系统的整体性能。主要应用场景包括:1) 将复杂任务分解为多个子任务,并分配给不同的线程,实现并行处理;2) 通过同步机制确保线程间协调一致,避免资源竞争和数据不一致问题。此外,理解并发编程还涉及锁机制、线程池和异步编程等关键技术。 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
  • 揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节
    揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节 ... [详细]
  • openGauss行存储核心架构及其页面组织详解
    行存储的核心架构和页面组织是实现DML操作、可见性判断及多种管理功能的基础。作为基于磁盘的存储引擎,行存储在设计上采用了段页式结构,以优化数据的存储和访问效率。这种设计不仅确保了数据的高效存储,还为行存储的各种高级功能提供了坚实的技术支持。 ... [详细]
author-avatar
caozhengweile_854
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有