背景:

当操作系统内存出现瓶颈时,我们便会重点排查那个应用占用内存过大。对于更深一步分析内存的使用,就进一步去了解内存结构,应用程序使用情况,以及内存如何分配、如何回收,这样你才能更好地确定内存的问题。

JVM 内存分配:

JVM(Java虚拟机)内存分配是指Java程序运行时,JVM对内存的分配和管理。JVM将内存划分为不同的区域,每个区域有不同的作用和生命周期。以下是JVM内存分配的详细解释:

  1. 方法区(Method Area):
    方法区用于存储类的结构信息,如类的字节码、常量池、静态变量、方法信息等。方法区在JVM启动时被创建,并且被所有线程共享。在JDK 8及之前,方法区是一个逻辑上的概念,实际上是通过永久代(Permanent Generation)实现的。但在JDK 8及以后,永久代被元空间(Metaspace)所取代。元空间使用本地内存来存储类的元数据。

  2. 堆(Heap):
    堆是用于存储对象实例的区域。所有在Java程序中创建的对象都存放在堆中。堆是线程共享的,被所有线程访问和操作。堆的大小可以通过启动参数进行调整。当堆中没有足够的空间容纳新创建的对象时,会触发垃圾回收(GC)来回收不再使用的对象,以释放内存。

    堆又可进一步划分为新生代(Young Generation)和老年代(Old Generation):

    • 新生代:新创建的对象首先被分配到新生代。新生代又分为一个Eden区和两个Survivor区(通常称为From区和To区)。
    • 老年代:当对象在新生代经过多次垃圾回收后仍然存活,它们会被晋升到老年代。老年代主要存放生命周期较长的对象。
  3. 栈(Stack):
    栈用于存储线程的方法调用和局部变量。每个线程在运行时都会创建一个栈帧,用于存储方法的局部变量表、操作数栈、动态链接、方法出口等信息。栈的大小是固定的,由虚拟机在启动时分配。栈是线程私有的,每个线程都有自己独立的栈。

    栈又可进一步划分为:

    • Java虚拟机栈(Java Virtual Machine Stack):存储Java方法执行的线程栈帧。
    • 本地方法栈(Native Method Stack):存储Native方法执行的线程栈帧。
  4. 本地方法栈(Native Method Stack):
    本地方法栈用于存储Java程序调用本地方法(使用JNI接口)时的栈信息。

  5. PC寄存器(Program Counter Register):
    PC寄存器存储当前线程执行的字节码指令的地址。

  6. 常量池(Constant Pool):
    常量池用于存放编译期生成的各种字面量和符号引用。

  7. 直接内存(Direct Memory):
    直接内存是堆外内存,不受JVM内存管理的限制。它通常由NIO(New Input/Output)库使用,通过与操作系统进行直接交互来提高I/O性能。

JVM会根据程序的运行情况动态调整各个内存区域的大小,并进行垃圾回收来释放不再使用的内存。对于编程者来说,特别需要注意的是堆和栈这两个区域,因为它们直接与对象和方法调用相关。而这其中堆空间占据着 JVM 中最大的存储区域,存放了很多对象,所以大多数基于 JVM 的内存调优也是对堆空间的调优。

JVM 垃圾回收:

在JVM内存管理中,内存被划分为新生代(Young Generation)和老年代(Old Generation),不同的垃圾回收算法和策略被应用于这两个代中。下面我将详细讲解新生代和老年代的垃圾回收过程。

新生代垃圾回收过程:

新生代是用于存放新创建的对象的区域,通常包括一个Eden区和两个Survivor区(通常称为From区和To区)。新生代垃圾回收主要包括以下几个阶段:

  Eden区:新创建的对象首先会被分配到Eden区。当Eden区满时,会触发一次新生代垃圾回收。

  标记阶段(Marking Phase):在标记阶段,垃圾回收器会标记所有在Eden区和From区中仍然存活的对象。

  复制阶段(Copying Phase):在复制阶段,垃圾回收器将标记的存活对象从Eden区和From区复制到To区。同时,它也会清空Eden区和From区的对象。

  交换Survivor区(Swap Survivor):在复制阶段完成后,垃圾回收器会交换From区和To区的角色,使得To区成为下一次垃圾回收时的From区。

  清除阶段(Sweeping Phase):在清除阶段,垃圾回收器会清空From区中的对象。此时,Eden区和To区是空的,可以用于下一轮的对象分配。

  新生代采用的是复制算法,该算法的优点是简单高效,但代价是需要将存活的对象复制到另一个区域,这对于存活对象较多的场景会带来一定的性能开销。

老年代垃圾回收过程:

老年代是用于存放存活时间较长的对象的区域,通常包括大对象、长时间存活的对象以及从新生代晋升过来的对象。老年代垃圾回收主要包括以下几个阶段:

  标记阶段(Marking Phase):与新生代的标记阶段类似,垃圾回收器会标记老年代中所有存活的对象。

  清除阶段(Sweeping Phase):在清除阶段,垃圾回收器会清除未标记的对象,并释放它们占用的内存空间。

  整理阶段(Compacting Phase):在整理阶段,垃圾回收器会将存活的对象向老年代的一端移动,从而消除内存碎片,使得老年代的空间得以连续。

  老年代的垃圾回收一般采用标记-清除-整理算法(Mark-Sweep-Compact),该算法通过整理阶段解决了老年代内存碎片的问题。

如何定位内存占用问题:

  已经知道jvm一些基础知识,那么该如何去定位瓶颈问题呢,主要有两个过程:

  • 观察 GC 的频次;
  • 定位占用内存的对象。

1.如何观察 GC 的频次?

JDK 自带的工具jstat,使用 jstat 来查看 GC 的频次,如下所示:

[root]# jstat -gc 26607 1000 3

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT

512.0  512.0  320.0   0.0   86016.0  27828.5   175104.0   157974.6  122840.0 116934.9 16128.0 15060.4   5328   37.311   4      1.042   38.353

512.0  512.0  320.0   0.0   86016.0  27981.9   175104.0   157974.6  122840.0 116934.9 16128.0 15060.4   5328   37.311   4      1.042   38.353

512.0  512.0  320.0   0.0   86016.0  28885.4   175104.0   157974.6  122840.0 116934.9 16128.0 15060.4   5328   37.311   4      1.042   38.353

输出项中有很多以 C 或者 U 结尾。S0 则代表第一个 Survivor 区,也就是我上文说的 From 区。通过以上的讲解,比如 S1C 和 S1U 则表示第二个 Survivor 区也就是 To 区的总容量和使用容量。

其他的输出选项含义。

  • EC / EU:Eden 区的总容量/已使用空间的大小。
  • OC / OU:老年代总容量/老年代已使用空间大小。
  • MC / MU:方法区总容量/方法区已使用容量大小。
  • CCSC / CCSU:压缩类总容量/压缩类空间使用大小。
  • YGC / YGCT:年轻代垃圾回收的次数/年轻代垃圾回收消耗时间。
  • FGC / FGCT: 老年代垃圾回收次数/老年代垃圾回收消耗时间。
  • GCT:垃圾回收消耗总时间。

通过GC的次数和时间可以初步判断系统中是否存在垃圾回收的问题。以下是一些常见的情况,当它们出现时,可能提示存在垃圾回收问题:

频繁的Full GC:
Full GC是指对整个堆内存进行垃圾回收的操作,它会导致应用程序的停顿时间较长。如果频繁发生Full GC,即使在短时间内,这可能意味着内存不足、内存泄漏或对象生命周期管理不当等问题。

频繁的Young GC:
Young GC是指对新生代进行垃圾回收的操作。如果频繁发生Young GC,尤其是在短时间内,可能表明新生代的内存空间不足、对象过早晋升到老年代、对象存活时间过长等问题。

长时间的停顿:
如果垃圾回收的停顿时间过长(比如几百毫秒以上),会导致应用程序的性能下降、响应时间延长等问题。长时间的停顿可能是因为Full GC或者垃圾回收器的配置不合理。

如何定位占用内存的对象:

1. 利用JDK 自带的 JVM 监控工具:jvisual,jvisual 能做的事情很多,监控内存泄漏、跟踪垃圾回收、执行时内存分析、CPU 线程分析等,而且通过图形化的界面指引就可以完成,具体用法

可自行参考。

2. 利用jmap 工具,通过jmap -histo pid | head -n 10, 查看该进程的对象等详细信息,可重点关注结果展示的第五列,找出属于自己的业务类

如何分析 JVM 内存瓶颈浅谈的更多相关文章

  1. 大数据学习--day13(字符串String--源码分析--JVM内存分析)

    字符串String--源码分析--JVM内存分析 String 类的对象 , 是不可变的字符串对象呢 这个不可变很重要,之后要讲的intern()也离不开它的不可变性. https://www.cnb ...

  2. JVM调优浅谈

    1.数据类型 java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:它代表的值就是数值本身,而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本 ...

  3. JVM调优浅谈(转)

    1.数据类型 java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:它代表的值就是数值本身,而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本 ...

  4. 利用MAT分析JVM内存问题,从入门到精通(二)

    上一篇文章MAT入门到精通(一)介绍了MAT的使用场景和基本概念,这篇文章开始介绍MAT的基本功能,后面还有两篇,一篇是MAT的高级功能,另一篇是MAT实战案例分析. 三.欢迎页 使用MAT打开一个h ...

  5. iOS之内存管理浅谈

    1.何为ARC ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被创建时retain count+1,在对象被re ...

  6. 技术分析 | 通过DML语句浅谈binlog和redo log

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1 ...

  7. jstat分析JVM内存

    zabbix: Jstat:gcutil:Old space utilization(%) S0  — Heap上的 Survivor space 0 区已使用空间的百分比S1  — Heap上的 S ...

  8. JVM内存状况查看方法和分析工具

    Java本身提供了多种丰富的方法和工具来帮助开发人员查看和分析GC及JVM内存的状况,同时开源界和商业界也有一些工具可用于查看.分析GC及JVM内存的状况.通过这些分析,可以排查程序中内存泄露的问题及 ...

  9. 配置JVM内存 查看内存工具

    一.配置JVM内存 1.配置JVM内存的參数有四个: -XmxJavaHeap最大值.默认值为物理内存的1/4.最佳设值应该视物理内存大小及计算机内其它内存开销而定. -XmsJavaHeap初始值, ...

  10. 深入JVM内存区域管理,值得你收藏

    JDK和JRE和JVM的关系 JDK(Java Development Kit)是程序开发者用来来编译.调试java程序用的开发工具包 JRE(JavaRuntimeEnvironment,Java运 ...

随机推荐

  1. 记一次排查:接口返回值写入excel后,从单元格copy出来的数据会带有多重引号的问题

    在项目里刚好有3个服务,同一个网关内层的3个服务,两个php的,一个golang的,为了提高负载以及进行分流,部分客户的接口调用会被网关自动分配到go服务. 恰好为了测试,我写了一个全量用户的生产.测 ...

  2. Prompt Engineering优化原则 - 以Webshell代码解释为例

    一.LLM prompt优化原则 本文围绕"PHP代码解释"这一任务,讨论LLM prompt优化原则. 代码样例如下: <?php echo "a5a5aa555 ...

  3. 瞄准程序员招聘痛点,ShowMeBug让面试代码操作可“回放”

    程序员虽然是建设互联网的职业之一,但他们的招聘工作的线上化却有不少难题. 疫情加速了市场对远程办公.远程面试.远程教学等模式的接受程度,但程序员招聘涉及到代码能力测试,甚至不同企业有不同的产品代码基础 ...

  4. 浅析 Jetty 中的线程优化思路

    作者:vivo 互联网服务器团队- Wang Ke 本文介绍了 Jetty 中 ManagedSelector 和 ExecutionStrategy 的设计实现,通过与原生 select 调用的对比 ...

  5. 图书商城项目练习①管理后台Vue2/ElementUI

    本系列文章是为学习Vue的项目练习笔记,尽量详细记录一下一个完整项目的开发过程.面向初学者,本人也是初学者,搬砖技术还不成熟.项目在技术上前端为主,包含一些后端代码,从基础的数据库(Sqlite).到 ...

  6. 记一次etcd全局锁使用不当导致的事故

    1.背景介绍 前两天,现场的同事使用开发的程序测试时,发现日志中报etcdserver: mvcc: database space exceeded,导致 etcd 无法连接.很奇怪,我们开发的程序只 ...

  7. redis雪崩问题解决

    缓存雪崩 出现的场景 缓存服务器宕机,没有设置持久化 介绍:缓存服务器宕机,没有设置持久化,导致缓存数据全部丢失,请求全部转发到数据库,造成数据库短时间内承受大量请求而崩掉. 缓存集中失效 缓存的ke ...

  8. 【SpringBoot】 集成 Ehcache

    SpringBoot ehcache 缓存 简介 EhCache 是一个纯 Java 的进程内缓存框架,具有快速.精干等特点, 是 Hibernate 中默认CacheProvider.Ehcache ...

  9. 深入探索 Django Rest Framework

    这篇文章会详细介绍Django REST Framework的核心组成部分,包括Serializers.ViewSets.Routers.权限和认证系统以及测试和调试工具.文章从基础开始,逐步深入,旨 ...

  10. 刷了一个月AI歌唱的视频 做一个大胆预测

    现在的AI热点转到ChatAI和AI唱歌去了 很好理解(现在每天在看Neuro的切片 感慨这才是看V的初心 可惜Neuro这个形象在创立的时候只是一个ChatAI 和游戏用的GameBOT并不是同一个 ...