【JVM系列1】JVM内存结构(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: JVM系列应该属于Java高阶的内容,本来是想等Java基础知识积累一段时候之后再学习,但是因为工作中需要跟进线上问题,所以急需补充这块知识,本文主要是对学习的知识简单做个记录,然后再记录分析问题的过程。

内存模型


对内存模型

内存模型几个重要点:

  • JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代和老年代,而非堆内存则为永久代。
  • 新生代Young和老年代Old默认占比是1:3。
  • 年轻代又会分为Eden和Survivor区,Survivor也会分为FromPlace和ToPlace,Eden、FromPlace和ToPlace的默认占比为 8:1:1。04L%FUW]F5{O)NF7(O[0214.png

GC类型

  • Minor GC/Young GC:针对新生代的垃圾收集;
  • Major GC/Old GC:针对老年代的垃圾收集。
  • Full GC:针对整个Java堆以及方法区的垃圾收集。

S$FVR{(P8[]I8L]M(`DL%C3.png

Minor GC工作原理

通常情况下,初次被创建的对象存放在新生代的Eden区,当第一次触发Minor GC,Eden区存活的对象被转移到Survivor区的某一块区域。以后再次触发Minor GC的时候,Eden区的对象连同一块Survivor区的对象一起,被转移到了另一块Survivor区。可以看到,这两块Survivor区我们每一次只使用其中的一块,这样也仅仅是浪费了一块Survivor区。

image.gifI8)C1CPWE%A8[OQM5559M%A.png

需要注意的2点:

  • 每经历过一次垃圾回收的对象,它的分代年龄就加1,当分代年龄达到15以后,就直接被存放到老年代中。
  • 给大对象分配内存的时候,Eden区已经没有足够的内存空间了,大对象就会直接进入老年代。


Full GC工作原理

老年代是存储长期存活的对象的,占满时就会触发我们最常听说的Full GC,期间会停止所有线程等待GC的完成。所以对于响应要求高的应用应该尽量去减少发生Full GC从而避免响应超时的问题。

image.gifQ5)}8HU]6PEFYBO_`(269)0.png

需要注意的几点:

  • Full GC耗时较长,发生次数远没有Minor GC频繁,太频繁意味着性能出现问题。
  • 标记-清除算法会产生大量内存碎片,以后如果需要为大对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次GC回收操作。

无论是Minor GC,还是Full GC,都会产生停顿现象,即Stop-The-World。Minor GC停顿时间较短,而Full GC耗时较长将导致长时间停顿、系统无响应,极大影响系统的性能。因此,Full GC日志的监控和性能分析在性能优化中极为重要。


GC日志


GC日志开启

偷个懒,直接贴网上的内容:

QKSM{BX{8P1[R43S$%(%)9P.png_6OQ6LPKV7B~0Q[X{0[UBMD.png


理解GC日志

Minor GC日志:image.gif

{00$@4@F7M]S9]A3IEG]WWF.png

Full GC日志:

$]`TPNCXTV)2@]{7HZF$Y$K.pngimage.gif


JVM的常用参数

image.gifP$5CRY9I`31Y8CLY`7`%M`2.png)7ADG`QH[884Y295J5FI[SD.png

其实还有一些打印及CMS方面的参数,这里就不以一一列举了。


GC日志分析与优化


线上机器配置:

  • 内存是16G
  • cpu 4核


优化前

再回到我们刚开始的截图:

image.gif}MS3R)JQ8YVUGGJP[P@IG~N.png

通过分析和计算,可以得到如下数据:

  • 老生代:5870976/(1024*1024) = 5.6G
  • 新生代:546176/1024 = 533M
  • Eden:273152/1024 = 266M
  • From:273024/1024 = 266M
  • To:273024/1024 = 266M

得出如下结论:

  • 新生代+老生代 = 5.6 + 533/1024 = 6.1G
  • 新生代:老生代 = 533 : (5.6*1024) = 1 : 10.7
  • Edem:From:To = 1:1:1

我们再看一下线上的配置:

image.gif

通过该配置再验证刚才的计算结果:

  • “-Xmx6000M -Xms6000M”,可以确定JVM内存大小为6000/1024=5.8G,之前计算的堆内存大小为6.1G,基本匹配(多余的可能分配给了永生代)
  • “ -Xmn800M”,可以确定新生代是800M,Edem+From+To为798M,基本匹配(为什么新生代533M和“Edem+From+To”798M有出入呢?)
  • “XX:SurvivorRatio=1”,这里有一个计算公式,大家可以自己百度一下,通过公式得到的结论是Edem:From:To = 1:1:1,和我们的计算结果完全匹配。

SurvivorRatio计算公式可见:https://blog.csdn.net/flyfhj/article/details/86630105


优化后

需要优化的点:

  • 目前内存使用不到一半,需要调整JVM内存大小;
  • Edem的内存太小,只有266M,这个是频繁Minor GC的主要原因,需要扩大改值;
  • 新生代:老生代的比值,需要从之前的1 : 10.7,调整到1:2
  • 新生代的Edem:From:To比值,需要从之前的1:1:1,调整到8:1:1

优化后的配置:

优化后的线上日志:

Heap before GC invocations=3 (full 1):
 par new generation   total 2764800K, used 2524705K [0x00000005cc000000, 0x0000000687800000, 0x0000000687800000)
  eden space 2457600K, 100% used [0x00000005cc000000, 0x0000000662000000, 0x0000000662000000)
  from space 307200K,  21% used [0x0000000674c00000, 0x0000000678d885c0, 0x0000000687800000)
  to   space 307200K,   0% used [0x0000000662000000, 0x0000000662000000, 0x0000000674c00000)
 concurrent mark-sweep generation total 5120000K, used 15613K [0x0000000687800000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 62116K, capacity 62680K, committed 63288K, reserved 1105920K
  class space    used 6639K, capacity 6781K, committed 6816K, reserved 1048576K
35.225: [GC (Allocation Failure) 35.225: [ParNew: 2524705K->184767K(2764800K), 0.2682475 secs] 2540319K->200381K(7884800K), 0.2683305 secs] [Times: user=1.05 sys=0.00, real=0.27 secs]

优化后的结果:

  • JVM内存大小为10000M,约9.7G
  • Edem的内存大小为2.6G,扩到原来的10倍
  • 新生代:老生代的比值为1:2
  • Edem:From:To的比值为8:1:1

目前这个方式应该还不是最优,因为JVM内存大小应该还可以继续扩大,目前需要在线上观察一段时间,然后再研究一下,如何进一步优化。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
24天前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
23天前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
30天前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
30天前
|
存储 Java 程序员
JVM自动内存管理之运行时内存区
这篇文章详细解释了JVM运行时数据区的各个组成部分及其作用,有助于理解Java程序运行时的内存布局和管理机制。
JVM自动内存管理之运行时内存区
|
18天前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
16 3
|
1月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
29天前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构
|
11天前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
29 0
|
2月前
|
运维 Java Linux
(九)JVM成神路之性能调优、GC调试、各内存区、Linux参数大全及实用小技巧
本章节主要用于补齐之前GC篇章以及JVM运行时数据区的一些JVM参数,更多的作用也可以看作是JVM的参数列表大全。对于开发者而言,能够控制JVM的部分也就只有启动参数了,同时,对于JVM的性能调优而言,JVM的参数也是基础。
|
2月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。

热门文章

最新文章