JVM 分析内存对象使用

简介: JVM 分析内存对象使用

JVM 内存分配

Java 文件一般是先编译成 class 结尾的文件,然后通过类加载器到 JVM 内存中。我们来看看 JVM 内存结构图,这样能够对它有个全局的了解。

image.png

1.本地方法栈

本地方法栈保存的是 native 方法的信息,native 方法就是 Java 调用非 Java 代码的接口,为什么会有这样的设置呢?简单来说,sun 的解释器是由 C 语言实现的,而 jre 又是基于 Java 语言,所以需要 native 方法来进行跨语言的调用。

2.Java 栈

Java 栈是常用的内存区域之一,它里面存放着基本数据类型和对象的引用,可能你不太清楚什么是对象的引用,拿上一讲中 HelloTester helloTester=new HelloTester() 为例,在 Java 栈中 HelloTester 是个引用,指向在堆空间中开辟的该对象的空间。

3.方法区(JDK 1.8+已经移除)

也叫作永久区,用来存储类信息,如上文描述的 HelloTester。值得注意的是方法区在 JDK 1.8 以上已经被元空间取代,并且元空间不在 JVM 中了,而是在本地内存中独立开辟存储空间。

4.程序计数器

可以认为是线程的信号指示器,它的作用是保存线程当前程序的执行位置,以保证多线程的切换。因为在多线程的情况下,CPU 并不是完成一个线程执行再去执行另外一个线程,而是不停地切换线程执行,这时程序计数器就可以发挥作用了。

5.堆

堆区域是 JVM 调优最重要的区域,堆中存放的数据很多是对象实例,如 HelloTester 的对象存储。堆空间占据着 JVM 中最大的存储区域,存放了很多对象,所以大多数基于 JVM 的内存调优也是对堆空间的调优。

堆空间并非取之不尽,如果一直存放总有用完的时候,所以对于有用的对象应当保存起来,无用的对象应当回收,为了更好地实现这一机制,JVM 将堆空间分成了新生代和老生代,如下图所示:

image.png

通过图,可以看到新生代和老年代的对比,Minor GC 发生在新生代,而 Full GC 发生在老年代。新生代分为三个区,一个 Eden 区和两个 Survivor 区。

先来看下 Eden 区的作用,大部分新生成的对象都是在 Eden 区,Eden 区满了之后便没有内存给新对象使用,Eden 区便会 Minor GC 回收无用内存,剩下的存活对象便会转移到 Survivor 区。

那两个 Survivor 区的作用分别是什么呢?两者其实是对称分布的,一个是 From 区,一个是 To 区。从 Eden 区存活下来的对象首先会被复制到 From 区,当 From 区满时,此时还存活的对象会被转移到 To 区,经历了多次的 Minor GC 后,还存活的对象就会被复制到老年代,老年代的 GC 一般叫作 FullGC 或者 MajorGC。

我们对比下新生代垃圾回收和老年代垃圾回收的区别,如下表所示:

image.png

如何定位内存占用问题
回到我们实际工作当中,当你发现 JVM 中使用的内存越来越多或者增长很快的时候,频繁 GC 的时候,应当如何去定位哪些对象导致的这些问题呢?

这其实涉及两个问题:

  • 如何去观察 GC 的频次;
  • 定位占用内存的对象。
1.如何观察 GC 的频次?

本部分我以 JDK 自带的工具来讲解,我一般使用 jstat 来查看 GC 的频次。首先我们来看下基本用法,如下所示:

[root@JD ~]# 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

我们来解析下终端输入的命令:

jstat -gc 26607 1000 3
  • 26607 代表查看的 PID 的 Java 进程号;
  • 1000 代表每隔 1000ms 也就是 1s 显示一次;
  • 3 代表一共显示三次。

接着我们再来看输出选项代表的含义有哪些?这个输出的信息含量比较大,不过信息是有对应关系的,比如 S0C 和 S0U:

  • 一般 C 结尾的代表总的容量大小或者计数的次数;
  • U 结尾代表已使用的容量大小。

这是通用的,你可以看到输出项中有很多以 C 或者 U 结尾。S0 则代表第一个 Survivor 区,也就是我上文说的 From 区。通过以上的讲解,我相信很多名词你不用死记硬背也能理解了,比如 S1C 和 S1U 则表示第二个 Survivor 区也就是 To 区的总容量和使用容量。

2.如何定位占用内存的对象?

这里我将推荐一个工具 jmap,通过 jmap 可以指定 Java 进程的 PID,查看该进程的对象、数量等等,接下来我做一个演示。

首先我们来查看进程号为 18658 的应用包,如下所示:

复制代码

[root@JD ~]# ps -ef|grep demo

root     18658     1  0 Dec09 ?后续省略

其中上述输出的第二列 18658 为进程号,然后将进程号通过命令组合可以查看以下信息:

[root@JD ~]# jmap -histo 18658|head -n 20

 num     #instances         #bytes  class name

----------------------------------------------

   1:        157619       18840672  [C

   2:          8326        8324360  [B

   3:        146319        3511656  java.lang.String

   4:          9224        2825584  [I

   5:         65733        2103456  com.example.demo.entity.User

   6:         62508        2000256  java.util.HashMap$Node

   7:         21868        1618832  [Ljava.lang.Object;
  • num 是编号;
  • instances 是生成的实例个数;
  • bytes 是实例占用的大小;
  • classs name 对象的类名。

其中 [C、[S、[I、[B 对应的类型如下所示:

[C is a char[]

[S is a short[]

[I is a int[]

[B is a byte[]

你注意下第五行,这是能够最直接看到的业务类,如果是业务对象尤其需要关注,看是否一直上升。

相关文章
|
8月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
745 55
|
7月前
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
494 3
|
3月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
292 5
|
3月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
3月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
136 1
|
9月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
733 6
|
9月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
1233 4
|
6月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
336 4
AI代理内存消耗过大?9种优化策略对比分析
|
8月前
|
Arthas 监控 Java
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
Arthas vmtool(从 jvm 里查询对象,执行 forceGc)
483 16
|
10月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
458 29
JVM简介—1.Java内存区域