04性能监控与调优篇(D5_JVM优化)

目录

一、我们为什么要对jvm做优化?

二、jvm的运行参数

1. 三种参数类型

1.1. 标准

1> 参数介绍

2> 实战

3> -server与-client参数

1.2. -X参数

1> 参数介绍

2> -Xint、-Xcomp、-Xmixed

1.3. -XX参数

-Xms与-Xmx参数

2. 查看jvm的运行参数

2.1. 运行 java 命令时打印参数

2.2. 查看正在运行的 jvm 参数

三、jvm的内存模型

jdk1.7的堆内存模型

jdk1.8的堆内存模型

为什么要废弃1.7中的永久区?

四、通过jstat命令进行查看堆内存使用情况

查看class加载统计

查看编译统计

垃圾回收统计

五、jmap的使用以及内存溢出分析

查看内存使用情况

查看内存中对象数量及大小

将内存使用情况dump到文件中

六、通过jhat对dump文件进行分析

七、通过MAT工具对dump文件进行分析

1. MAT工具介绍

2. 下载安装

3. 使用

4. 内存溢出的定位与分析(实战)

4.1. 模拟内存溢出

4.2. 运行测试

4.3. 导入到MAT工具中进行分析

八、jstack的使用

1. 线程的状态

2. 死锁问题(实战)

2.1. 构造死锁

2.2. 在linux上运行

2.3. 使用jstack进行分析

九、VisualVM工具的使用

1. 启动

2. 查看本地进程

3. 查看CPU、内存、类、线程运行信息

4. 查看线程详情

5. 抽样器

6. 监控远程的jvm

6.1. 什么是JMX?

6.2. 监控远程的tomcat

6.3. 使用VisualJVM连接远程tomcat


一、我们为什么要对jvm做优化?

在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们可能将有下面 的需求:

  • 运行的应用“卡住了”,日志不输出,程序没有反应
  • 服务器的CPU负载突然升高
  • 在多线程应用下,如何分配线程的数量?
  • ……

在本次课程中,我们将对jvm有更深入的学习,我们不仅要让程序能跑起来,而且是可以跑的更快!

可以分析解决在生产环境中所遇到的各种“棘手”的问题。

说明:本套课程使用的jdk版本为1.8。

二、jvm的运行参数

在jvm中有很多的参数可以进行设置,这样可以让jvm在各种环境中都能够高效的运行。 绝大部分的参数保持默认即可。

1. 三种参数类型

jvm的参数类型分为三类,分别是:

  1. 标准参数
    • -help
    • -version
  1. -X参数 (非标准参数)
    • -Xint
    • -Xcomp
  1. -XX参数(使用率较高)
    • -XX:newSize
    • -XX:+UseSerialGC

1.1. 标准

1> 参数介绍

jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java -help 检索出所 有的标准参数。

[root@node01 ~]# java -help 

用法: java [-options] class [args...] (执行类) 或 java [-options] -jar jarfile [args...] 
 (执行 jar 文件) 

其中选项包括: 
 -d32 使用 32 位数据模型 (如果可用) 
 -d64 使用 64 位数据模型 (如果可用) 
 -server 选择 "server" VM 默认 VM 是 server, 因为您是在服务器类计算机上运行。 
 -cp <目录和 zip/jar 文件的类搜索路径> 
 -classpath <目录和 zip/jar 文件的类搜索路径> 用 : 分隔的目录, JAR 档案 和 ZIP 

档案列表, 用于搜索类文件。 
 -D<名称>=<值> 设置系统属性 
 -verbose:[class|gc|jni] 启用详细输出 
 -version 输出产品版本并退出 
 -version:<值> 警告: 此功能已过时, 将在 未来发行版中删除。 需要指定的版本才能运行 
 -showversion 输出产品版本并继续 
 -jre-restrict-search | -no-jre-restrict-search 警告: 此功能已过时, 将在 未来
发行版中删除。 在版本搜索中包括/排除用户专用 JRE 
 -? -help 输出此帮助消息 
 -X 输出非标准选项的帮助 
 -ea[:<packagename>...|:<classname>] 
 -enableassertions[:<packagename>...|:<classname>] 按指定的粒度启用断言 
 -da[:<packagename>...|:<classname>] 
 -disableassertions[:<packagename>...|:<classname>] 禁用具有指定粒度的断言 
 -esa | -enablesystemassertions 启用系统断言 
 -dsa | -disablesystemassertions 禁用系统断言 
 -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 

另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help 
 -agentpath:<pathname>[=<选项>] 按完整路径名加载本机代理库 
 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 
java.lang.instrument 
 -splash:<imagepath> 使用指定的图像显示启动屏幕
2> 实战

实战1:查看jvm版本

[root@node01 ~]# java -version 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
# -showversion参数是表示,先打印版本信息,再执行后面的命令,在调试时非常有用, 后面会使用到

实战2:通过-D设置系统属性参数

public class TestJVM { 
    public static void main(String[] args) { 
        String str = System.getProperty("str"); 
        if (str == null) { 
            System.out.println("kkb"); 
       } else { 
            System.out.println(str); 
       } 
   } 
}

进行编译、测试:

#编译 
[root@node01 test]# javac TestJVM.java 
#测试
[root@node01 test]# java TestJVM 
kkb 
[root@node01 test]# java -Dstr=123 TestJVM 
123
3> -server与-client参数

可以通过-server或-client设置jvm的运行参数。

  • 它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。
  • Client VM相对来讲会保守一些,初始堆空间会小一些,使用串行的垃圾回收器,它的目标是为了 让JVM的启动速度更快,但运行速度会比Serverm模式慢些。
  • JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。
  • 32位操作系统
    • 如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。
    • 如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server 模式,否则使用client模式。
  • 64位操作系统 只有server类型,不支持client类型。

测试:

[root@node01 test]# java -client -showversion TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
kkb 
[root@node01 test]# java -server -showversion TestJVM 

java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
kkb 

#由于机器是64位系统,所以不支持client模式

1.2. -X参数

1> 参数介绍

jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java - X查看非标准参数。

[root@node01 test]# java -X
 -Xmixed 混合模式执行 (默认) 
 -Xint 仅解释模式执行 
 -Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件> 设置搜索路径以引导类和资源 
 -Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件> 附加在引导类路径末尾 
 -Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件> 置于引导类路径之前 
 -Xdiag 显示附加诊断消息 
 -Xnoclassgc 禁用类垃圾收集 
 -Xincgc 启用增量垃圾收集 
 -Xloggc:<file> 将 GC 状态记录在文件中 (带时间戳) 
 -Xbatch 禁用后台编译 
 -Xms<size> 设置初始 Java 堆大小 
 -Xmx<size> 设置最大 Java 堆大小 
 -Xss<size> 设置 Java 线程堆栈大小 
 -Xprof 输出 cpu 配置文件数据 
 -Xfuture 启用最严格的检查, 预期将来的默认值 
 -Xrs 减少 Java/VM 对操作系统信号的使用 (请参阅文档) 
 -Xcheck:jni 对 JNI 函数执行其他检查 
 -Xshare:off 不尝试使用共享类数据 
 -Xshare:auto 在可能的情况下使用共享类数据 (默认) 
 -Xshare:on 要求使用共享类数据, 否则将失败。 
 -XshowSettings 显示所有设置并继续 
 -XshowSettings:all显示所有设置并继续 
 -XshowSettings:vm 显示所有与 vm 相关的设置并继续 
 -XshowSettings:properties 显示所有属性设置并继续 
 -XshowSettings:locale 显示所有与区域设置相关的设置并继续 

-X 选项是非标准选项, 如有更改, 恕不另行通知。
2> -Xint、-Xcomp、-Xmixed
  • 在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运 行速度,通常低10倍或更多。
  • -Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把所有的字节码编译成本地代码,从 而带来最大程度的优化。 然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代 码都进行编译的话,对于一些只执行一次的代码就没有意义了。
  • -Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模 式,也是推荐使用的模式。

示例:强制设置运行模式

#强制设置为解释模式 
[root@node01 test]# java -showversion -Xint TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, interpreted mode) 
kkb 

#强制设置为编译模式 
[root@node01 test]# java -showversion -Xcomp TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, compiled mode) 
kkb 

#注意:编译模式下,第一次执行会比解释模式下执行慢一些,注意观察。
#默认的混合模式 
[root@node01 test]# java -showversion TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
kkb

1.3. -XX参数

-XX参数也是非标准参数,主要用于jvm的调优和debug操作。

-XX参数的使用有2种方式,一种是boolean类型,一种是非boolean类型:

  • boolean类型格式:-XX:[+-] 如:-XX:+DisableExplicitGC 表示禁用手动调用gc操作,也就是说调用System.gc()无效
  • 非boolean类型格式:-XX: 如:-XX:NewRatio=1 表示新生代和老年代的比值

用法:

[root@node01 test]# java -showversion -XX:+DisableExplicitGC TestJVM 
java version "1.8.0_141" 
Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
kkb
-Xms与-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。

-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。

-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。

适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。

示例:

[root@node01 test]# java -Xms512m -Xmx2048m TestJVM
kkb

2. 查看jvm的运行参数

有些时候我们需要查看 jvm 的运行参数,这个需求可能会存在 2 种情况:

第一,运行 java 命令时打印出运行参数;

第二,查看正在运行的 java 进程的参数;

2.1. 运行 java 命令时打印参数

运行 java 命令时打印参数,需要添加 -XX:+PrintFlagsFinal 参数即可。

[root@node01 test]# java -XX:+PrintFlagsFinal -version 
[Global flags] 
 uintx AdaptiveSizeDecrementScaleFactor = 4 
     {product} 
 uintx AdaptiveSizeMajorGCDecayTimeScale = 10 
     {product} 
 uintx AdaptiveSizePausePolicy = 0 
     {product} 
 uintx AdaptiveSizePolicyCollectionCostMargin = 50 
     {product} 
 uintx AdaptiveSizePolicyInitializingSteps = 20 
     {product} 
 uintx AdaptiveSizePolicyOutputInterval = 0 
     {product} 
 uintx AdaptiveSizePolicyWeight = 10 
     {product} 
 uintx AdaptiveSizeThroughPutPolicy = 0 
 {product} 
 uintx AdaptiveTimeWeight = 25 
 {product} 
 bool AdjustConcurrency = false 
 {product} 
 bool AggressiveOpts = false 
 {product} 
 intx AliasLevel = 3 
 {C2 product} 
 bool AlignVector = true 
 {C2 product} 
 intx AllocateInstancePrefetchLines = 1 
 {product} 
 intx AllocatePrefetchDistance = 256 
 {product} 
 intx AllocatePrefetchInstr = 0 
 {product} 
 
 …………………………略………………………………………… 
 
 bool UseXmmI2D = false

 {ARCH product} 
 bool UseXmmI2F = false 
 {ARCH product} 
 bool UseXmmLoadAndClearUpper = true 
 {ARCH product} 
 bool UseXmmRegToRegMoveAll = true 
 {ARCH product} 
 bool VMThreadHintNoPreempt = false 
 {product} 
 intx VMThreadPriority = -1 
 {product} 
 intx VMThreadStackSize = 1024 
 {pd product} 
 intx ValueMapInitialSize = 11 
 {C1 product} 
 intx ValueMapMaxLoopSize = 8 
 {C1 product} 
 intx ValueSearchLimit = 1000 
 {C2 product} 
 bool VerifyMergedCPBytecodes = true 
 {product} 
 bool VerifySharedSpaces = false 
 {product} 
 intx WorkAroundNPTLTimedWaitHang = 1 
 {product} 
 uintx YoungGenerationSizeIncrement = 20 
 {product} 
 uintx YoungGenerationSizeSupplement = 80 
 {product} 
 uintx YoungGenerationSizeSupplementDecay = 8 
 {product} 
 uintx YoungPLABSize = 4096 
 {product} 
 bool ZeroTLAB = false 
 {product} 
 intx hashCode = 5
 {product} 
 
 java version "1.8.0_141" 
 Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
 Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

由上述的信息可以看出,参数有boolean类型和数字类型,值的操作符是=或:=,分别代 表默认值和被 修改的值。

示例:


java -XX:+PrintFlagsFinal -XX:+VerifySharedSpaces -version 
 intx ValueMapInitialSize = 11 {C1 product} 
 intx ValueMapMaxLoopSize = 8 {C1 product} 
 intx ValueSearchLimit = 1000 {C2 product} 
 bool VerifyMergedCPBytecodes = true {product} 
 bool VerifySharedSpaces := true {product} 
 intx WorkAroundNPTLTimedWaitHang = 1 {product} 
 uintx YoungGenerationSizeIncrement = 20 {product} 
 uintx YoungGenerationSizeSupplement = 80 {product} 
 uintx YoungGenerationSizeSupplementDecay = 8 {product} 
 uintx YoungPLABSize = 4096 {product} 
 bool ZeroTLAB = false {product} 
 intx hashCode = 5 {product} 
 
 java version "1.8.0_141" 
 Java(TM) SE Runtime Environment (build 1.8.0_141-b15) 
 Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode) 
 
 #可以看到VerifySharedSpaces这个参数已经被修改了。

2.2. 查看正在运行的 jvm 参数

如果想要查看正在运行的jvm就需要借助于jinfo命令查看。

首先,启动一个tomcat用于测试,来观察下运行的jvm参数。

cd /tmp/ 
rz 上传 
tar -xvf apache-tomcat-7.0.57.tar.gz 
cd apache-tomcat-7.0.57 
cd bin/ 
./startup.sh 
#http://192.168.40.133:8080/ 进行访问

访问成功:

#查看所有的参数,用法:jinfo -flags <进程id> 
#通过jps 或者 jps -l 查看java进程 
[root@node01 bin]# jps 
6346 Jps 
6219 Bootstrap 
[root@node01 bin]# jps -l 
6358 sun.tools.jps.Jps 
6219 org.apache.catalina.startup.Bootstrap 
[root@node01 bin]# 
[root@node01 bin]# jinfo -flags 6219 
Attaching to process ID 6219, please wait... 
Debugger attached successfully. 
Server compiler detected. 
JVM version is 25.141-b15 
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=31457280 
-XX:MaxHeapSize=488636416 -XX:MaxNewSize=162529280 - 
XX:MinHeapDeltaBytes=524288 -XX:NewSize=10485760 -XX:OldSize=20971520 - 
XX:+UseCompressedClassPointers -XX:+UseCompressedOops - 
XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC Command line: 
-Djava.util.logging.config.file=/tmp/apache-tomcat
7.0.57/conf/logging.properties
- 
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager - 
Djava.endorsed.dirs=/tmp/apache-tomcat-7.0.57/endorsed - 
Dcatalina.base=/tmp/apache-tomcat-7.0.57 -Dcatalina.home=/tmp/apache
tomcat-7.0.57
-Djava.io.tmpdir=/tmp/apache-tomcat-7.0.57/temp 
#查看某一参数的值,用法:jinfo -flag <参数名> <进程id> 
[root@node01 bin]# jinfo -flag MaxHeapSize 6219 
-XX:MaxHeapSize=488636416

三、jvm的内存模型

jvm的内存模型在1.7和1.8有较大的区别,虽然本套课程是以1.8为例进行讲解,但是我们也是需要对 1.7的内存模型有所了解,

所以接下里,我们将先学习1.7再学习1.8的内存模型。

jdk1.7的堆内存模型

  • Young 年轻区(代) Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中, 某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时 候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集 后,任然存活于Survivor的对象将被移动到Tenured区间。
  • Tenured 年老区Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定 的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存 中的对象往往会被转移到这一区间。
  • Perm 永久区Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很 多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能 是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象 保存在了permq中,这种情况下,一般重新启动应用服务器可以解决问题。
  • Virtual区最大内存和初始内存的差值,就是Virtual区。

jdk1.8的堆内存模型

由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。

年轻代:Eden + 2*Survivor

年老代:OldGen

在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。

需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是 与1.7的永久代最大的区别所在。

为什么要废弃1.7中的永久区?

官网给出了解释:JEP 122: Remove the Permanent Generation

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent

generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent

generation.

移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配 置永久代。

现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异常 java.lang.OutOfMemoryError:PermGen。

基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。

四、通过jstat命令进行查看堆内存使用情况

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:

jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数] 

查看class加载统计

[root@node01 ~]# jps 
7080 Jps 
6219 Bootstrap 

[root@node01 ~]# jstat -class 6219 
Loaded Bytes Unloaded Bytes Time 
3273 7122.3 0 0.0 3.98

说明:

  • Loaded:加载class的数量
  • Bytes:所占用空间大小
  • Unloaded:未加载数量
  • Bytes:未加载占用空间
  • Time:时间

查看编译统计

[root@node01 ~]# jstat -compiler 6219 
Compiled Failed Invalid Time FailedType FailedMethod 
2376 1 0 8.04 1 org/apache/tomcat/util/IntrospectionUtils 
setProperty

说明:

  • Compiled:编译数量。
  • Failed:失败数量
  • Invalid:不可用数量
  • Time:时间
  • FailedType:失败类型
  • FailedMethod:失败的方法

垃圾回收统计

[root@node01 ~]# jstat -gc 6219 
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 
9216.0 8704.0 0.0 6127.3 62976.0 3560.4 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323 
#也可以指定打印的间隔和次数,每1秒中打印一次,共打印5次 
[root@node01 ~]# jstat -gc 6219 1000 5 
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323 
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323 
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323 
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323 
9216.0 8704.0 0.0 6127.3 62976.0 3917.3 33792.0 20434.9 23808.0 23196.1 
2560.0 2361.6 7 1.078 1 0.244 1.323

说明:

  • S0C:第一个Survivor区的大小(KB)
  • S1C:第二个Survivor区的大小(KB)
  • S0U:第一个Survivor区的使用大小(KB)
  • S1U:第二个Survivor区的使用大小(KB)
  • EC:Eden区的大小(KB)
  • EU:Eden区的使用大小(KB)
  • OC:Old区大小(KB)
  • OU:Old使用大小(KB)
  • MC:方法区大小(KB)
  • MU:方法区使用大小(KB)
  • CCSC:压缩类空间大小(KB)
  • CCSU:压缩类空间使用大小(KB)
  • YGC:年轻代垃圾回收次数
  • YGCT:年轻代垃圾回收消耗时间
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收消耗时间
  • GCT:垃圾回收消耗总时间

五、jmap的使用以及内存溢出分析

前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,

如:内存使用 情况的汇总、对内存溢出的定位与分析。

查看内存使用情况

[root@node01 ~]# jmap -heap 6219 
Attaching to process ID 6219, please wait... 
Debugger attached successfully. 
Server compiler detected. 
JVM version is 25.141-b15 

using thread-local object allocation. 
Parallel GC with 2 thread(s) 

Heap Configuration: #堆内存配置信息 
 MinHeapFreeRatio = 0 
 MaxHeapFreeRatio = 100 
 MaxHeapSize = 488636416 (466.0MB) 
 NewSize = 10485760 (10.0MB) 
 MaxNewSize = 162529280 (155.0MB) 
 OldSize = 20971520 (20.0MB) 
 NewRatio = 2 
 SurvivorRatio = 8 
 MetaspaceSize = 21807104 (20.796875MB) 
 CompressedClassSpaceSize = 1073741824 (1024.0MB) 
 MaxMetaspaceSize = 17592186044415 MB 
 G1HeapRegionSize = 0 (0.0MB) 
 
Heap Usage: # 堆内存的使用情况 
PS Young Generation #年轻代 
Eden Space: 
 capacity = 123731968 (118.0MB) 
 used = 1384736 (1.320587158203125MB) 
 free = 122347232 (116.67941284179688MB) 
 1.1191416594941737% used 
From Space: 
 capacity = 9437184 (9.0MB) 
 used = 0 (0.0MB) 
 free = 9437184 (9.0MB) 
 0.0% used 
To Space: 
 capacity = 9437184 (9.0MB) 
 used = 0 (0.0MB) 
 free = 9437184 (9.0MB) 
 
 0.0% used
PS Old Generation #年老代 
 capacity = 28311552 (27.0MB) 
 used = 13698672 (13.064071655273438MB) 
 free = 14612880 (13.935928344726562MB) 
 48.38545057508681% used 
 
13648 interned Strings occupying 1866368 bytes.

查看内存中对象数量及大小

#查看所有对象,包括活跃以及非活跃的 jmap -histo <pid> | more #查看活跃对象 jmap 
-histo:live <pid> | more [root@node01 ~]# jmap -histo:live 6219 | more num 
#instances #bytes class name ---------------------------------------------- 1: 
37437 7914608 [C 2: 34916 837984 java.lang.String 3: 884 654848 [B 4: 17188 
550016 java.util.HashMap$Node 5: 3674 424968 java.lang.Class 6: 6322 395512 
[Ljava.lang.Object; 7: 3738 328944 java.lang.reflect.Method 8: 1028 208048 
[Ljava.util.HashMap$Node; 9: 2247 144264 [I 10: 4305 137760 
java.util.concurrent.ConcurrentHashMap$Node 11: 1270 109080 
[Ljava.lang.String; 12: 64 84128 
[Ljava.util.concurrent.ConcurrentHashMap$Node; 13: 1714 82272 
java.util.HashMap 14: 3285 70072 [Ljava.lang.Class; 15: 2888 69312 
java.util.ArrayList 16: 3983 63728 java.lang.Object 17: 1271 61008 
org.apache.tomcat.util.digester.CallMethodRule 18: 1518 60720 
java.util.LinkedHashMap$Entry 19: 1671 53472 
com.sun.org.apache.xerces.internal.xni.QName 20: 88 50880 
[Ljava.util.WeakHashMap$Entry; 21: 618 49440 java.lang.reflect.Constructor 
22: 1545 49440 java.util.Hashtable$Entry 23: 1027 41080 
java.util.TreeMap$Entry 24: 846 40608 
org.apache.tomcat.util.modeler.AttributeInfo 25: 142 38032 [S
26: 946 37840 java.lang.ref.SoftReference 27: 226 36816 [[C。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 #对象说明 B byte C char D 
double F float I int J long Z boolean [ 数组,如[I表示int[] [L+类名 其他对象

将内存使用情况dump到文件中

有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也是支持dump到文 件中的。

#用法: 
jmap -dump:format=b,file=dumpFileName <pid> 
#示例 
jmap -dump:format=b,file=/tmp/dump.dat 6219

可以看到已经在/tmp下生成了dump.dat的文件。

六、通过jhat对dump文件进行分析

在上一小节中,我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,

这时我 们可以借助于jhat工具进行查看。

用法:

jhat -port <port> <file> 

示例:

[root@node01 tmp]# jhat -port 9999 /tmp/dump.dat 
Reading from /tmp/dump.dat... 
Dump file created Mon Sep 10 01:04:21 CST 2018 
Snapshot read, resolving... 
Resolving 204094 objects... 
Chasing references, expect 40 
dots........................................ 
Eliminating duplicate references........................................ 
Snapshot resolved. 
Started HTTP server on port 9999 
Server is ready. 

打开浏览器进行访问:http://192.168.40.133:9999/

在最后面有OQL查询功能。

七、通过MAT工具对dump文件进行分析

1. MAT工具介绍

MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找

内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行 分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾

收集器的回收工作,并可以通过报 表直观的查看到可能造成这种结果的对象。

官网地址:Memory Analyzer (MAT) | The Eclipse Foundation

2. 下载安装

下载地址:Not Found

将下载得到的MemoryAnalyzer-1.8.0.20180604-win32.win32.x86_64.zip进行解压:

3. 使用

查看对象以及它的依赖:

查看可能存在内存泄露的分析:

4. 内存溢出的定位与分析(实战)

内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读 取超大的文件等等,都可能会造

成内存溢出。

如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正 常还是非正常 情况,如果是正常的需求,就应该

考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修 改,修复这个bug。

首先,我们得先学会如何定位问题,然后再进行分析。如何定位问题呢,我们需要借助于jmap与MAT工 具进行定位分析。

接下来,我们模拟内存溢出的场景。

4.1. 模拟内存溢出

编写代码,向List集合中添加100万个字符串,每个字符串由1000个UUID组成。如果程序能够正常执 行,最后打印ok。

package com.wclass.jvm; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.UUID; 

public class TestJvmOutOfMemory { 
    public static void main(String[] args) { 
        List<Object> list = new ArrayList<>(); 
        for (int i = 0; i < 10000000; i++) { 
            String str = ""; 
            for (int j = 0; j < 1000; j++) { 
                str += UUID.randomUUID().toString(); 
           }
            list.add(str); 
       }
        System.out.println("ok");
   } 
}

为了演示效果,我们将设置执行的参数,这里使用的是Idea编辑器。

#参数如下: 
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

4.2. 运行测试

测试结果如下:

java.lang.OutOfMemoryError: Java heap space 
Dumping heap to java_pid5348.hprof ... 
Heap dump file created [8137186 bytes in 0.032 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
at java.util.Arrays.copyOf(Arrays.java:3332) 
at 
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilde
r.java:124) 
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) 
at java.lang.StringBuilder.append(StringBuilder.java:136) 
at com.wclass.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14) 
Process finished with exit code 1

可以看到,当发生内存溢出时,会dump文件到java_pid5348.hprof。

4.3. 导入到MAT工具中进行分析

可以看到,有91.03%的内存由Object[]数组占有,所以比较可疑。

分析:这个可疑是正确的,因为已经有超过90%的内存都被它占有,这是非常有可能出现内存溢出的。

查看详情:

可以看到集合中存储了大量的uuid字符串。

八、jstack的使用

有些时候我们需要查看下jvm中的线程执行情况,

比如,发现服务器的CPU的负载突然增高了、出现了死 锁、死循环等,我们该如何分析呢?

由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,

所以就需要看下jvm的内部 线程的执行情况,然后再进行分析查找出原因。

这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且 打印出来:

用法:jstack

[root@node01 bin]# jstack 2203 

1. 线程的状态

在Java中线程的状态一共被分成6种:

  • 初始态(NEW) 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
  • 运行态(RUNNABLE),在Java中,运行态包括 就绪态 和 运行态。
    • 就绪态该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。 所有就绪态的线程存放在就绪队列中。
    • 运行态获得CPU执行权,正在执行的线程。 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
  • 阻塞态(BLOCKED) 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。 而在Java中,阻塞态专指请求锁失败时进入的状态。 由一个阻塞队列存放所有阻塞态的线程。 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
  • 等待态(WAITING) 当前线程中调用wait、join、park函数时,当前线程就会进入等待态。 也有一个等待队列存放所有等待态的线程。 线程处于等待态表示它需要等待其他线程的指示才能继续运行。 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
  • 超时等待态(TIMED_WAITING) 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状 态; 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒; 进入该状态后释放CPU执行权 和 占有的资源。 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
  • 终止态(TERMINATED) 线程执行结束后的状态。

2. 死锁问题(实战)

如果在生产环境发生了死锁,我们将看到的是部署的程序没有任何反应了,这个时候我们可以借助 jstack进行分析,

下面我们实战下查找死锁的原因。

2.1. 构造死锁

编写代码,启动2个线程,Thread1拿到了obj1锁,准备去拿obj2锁时,obj2已经被Thread2锁定,所 以发送了死锁。

public class TestDeadLock { 
    private static Object obj1 = new Object(); 
    private static Object obj2 = new Object(); 
    
    public static void main(String[] args) { 
        new Thread(new Thread1()).start(); 
        new Thread(new Thread2()).start(); 
    }
    
    private static class Thread1 implements Runnable{ 
        @Override 
        public void run() { 
            synchronized (obj1){ 
                System.out.println("Thread1 拿到了 obj1 的锁!"); 
                try {
                    // 停顿2秒的意义在于,让Thread2线程拿到obj2的锁 

                    Thread.sleep(2000); 
               } catch (InterruptedException e) { 
                    e.printStackTrace(); 
               }
               synchronized (obj2){ 
                    System.out.println("Thread1 拿到了 obj2 的锁!"); 
               } 
            } 
        } 
    }
    
    private static class Thread2 implements Runnable{ 
        @Override 
        public void run() { 
       synchronized (obj2){ 
       System.out.println("Thread2 拿到了 obj2 的锁!"); 
                try {
                    // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁

                    Thread.sleep(2000); 
               } catch (InterruptedException e) { 
                    e.printStackTrace(); 
               }
                synchronized (obj1){ 
                    System.out.println("Thread2 拿到了 obj1 的锁!"); 
               } 
           } 
       } 
   } 
}

2.2. 在linux上运行

[root@node01 test]# javac TestDeadLock.java 
[root@node01 test]# ll 
总用量 28
-rw-r--r--. 1 root root 184 9月 11 10:39 TestDeadLock$1.class 
-rw-r--r--. 1 root root 843 9月 11 10:39 TestDeadLock.class 
-rw-r--r--. 1 root root 1567 9月 11 10:39 TestDeadLock.java 
-rw-r--r--. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread1.class 
-rw-r--r--. 1 root root 1078 9月 11 10:39 TestDeadLock$Thread2.class 
-rw-r--r--. 1 root root 573 9月 9 10:21 TestJVM.class 
-rw-r--r--. 1 root root 261 9月 9 10:21 TestJVM.java 
[root@node01 test]# java TestDeadLock 

Thread1 拿到了 obj1 的锁! 
Thread2 拿到了 obj2 的锁! 
#这里发生了死锁,程序一直将等待下去

2.3. 使用jstack进行分析

[root@node01 ~]# jstack 3256

可以清晰的看到:

Thread2获取了 <0x00000000f655dc50> 的锁,等待获取 <0x00000000f655dc40>这个锁

Thread1获取了 <0x00000000f655dc40> 的锁,等待获取 <0x00000000f655dc50>这个锁 由此可见,发生了死锁。

九、VisualVM工具的使用

VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,

反向查 看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。

  • 内存信息
  • 线程信息
  • Dump堆(本地进程)
  • Dump线程(本地进程)
  • 打开堆Dump。堆Dump可以用jmap来生成。
  • 打开线程Dump
  • 生成应用快照(包含内存信息、线程信息等等)
  • 性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内 存,检查哪些类占用内存多)
  • ……

1. 启动

在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可。

2. 查看本地进程

3. 查看CPU、内存、类、线程运行信息

4. 查看线程详情

也可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令。

发现,显示的内容是一样的。

5. 抽样器

抽样器可以对CPU、内存在一段时间内进行抽样,以供分析。

6. 监控远程的jvm

VisualJVM不仅是可以监控本地jvm进程,还可以监控远程的jvm进程,需要借助于JMX技术

6.1. 什么是JMX?

JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理 功能的框架。

JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝 集成的系统、网络和服务管理应用。

6.2. 监控远程的tomcat

想要监控远程的tomcat,就需要在远程的tomcat进行对JMX配置,方法如下:

#在tomcat的bin目录下,修改catalina.sh,添加如下的参数 
JAVA_OPTS="-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=9999 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false" 

#这几个参数的意思是: 
#-Dcom.sun.management.jmxremote :允许使用JMX远程管理 
#-Dcom.sun.management.jmxremote.port=9999 :JMX远程连接端口 
#-Dcom.sun.management.jmxremote.authenticate=false :不进行身份认证,任何用户都可
以连接 
#-Dcom.sun.management.jmxremote.ssl=false :不使用ssl

保存退出,重启tomcat。

6.3. 使用VisualJVM连接远程tomcat

添加远程主机:

在一个主机下可能会有很多的jvm需要监控,所以接下来要在该主机上添加需要监控的jvm:

连接成功。使用方法和前面就一样了,就可以和监控本地jvm进程一样,监控远程的tomcat进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/970794.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手&#xff08;Copilot、DeepSeek、GPT-4o Mini&#xff09; &#x1f4ca; 引言 近年来&#xff0c;AI 编程助手已成为开发者的高效工具&#xff0c;它们可以加速代码编写、优化代码结构&#xff0c;并提供智能提示。本文介绍如何在 IntelliJ I…

嵌入式软件、系统、RTOS(高软23)

系列文章目录 4.2嵌入式软件、系统、RTOS 文章目录 系列文章目录前言一、嵌入式软件二、嵌入式系统三、嵌入式系统分类四、真题总结 前言 本节讲明嵌入式相关知识&#xff0c;包括软件、系统。 一、嵌入式软件 二、嵌入式系统 三、嵌入式系统分类 四、真题 总结 就是高软笔记…

spring 学习 (注解)

目录 前言 常用的注解 须知 1 Conponent注解 demo&#xff08;案例&#xff09; 2 ControllerServiceRepository demo(案例&#xff09; 3 ScopeLazyPostConstructPreDestroy demo(案例&#xff09; 4 ValueAutowiredQualifierResource demo(案例&#xff09; 5 Co…

C语言中qsort函数使用技巧

在C语言的标准库中&#xff0c; qsort 函数是一个强大的通用排序函数&#xff0c;它采用快速排序算法&#xff0c;能够高效地对各种数据类型的数组进行排序。掌握 qsort 函数的使用技巧&#xff0c;对于提升程序的效率和代码的简洁性至关重要。 一、qsort函数基本介绍 qsort 函…

python+deepseek进行个股分析

背景&#xff1a;deepseek无法获取最新的行情数据&#xff0c;需要手动喂给它 一 用python获取最新的个股数据 请参考我的另外一篇文章&#xff1a;[python获取个股的行情数据]&#xff08;稍微改造下导出数据到excel中&#xff09;(https://blog.csdn.net/weixin_43006743/…

Golang官方编程指南

文章目录 1. Golang 官方编程指南2. Golang 标准库API文档 1. Golang 官方编程指南 Golang 官方网站&#xff1a;https://go.dev/ 点击下一步&#xff0c;查看官方手册怎么用 https://tour.go-zh.org/welcome/1 手册中的内容比较简单 go语言是以包的形式化管理函数的 搜索包名…

linux常用命令大全(包括抓包、网络检测、路由等,做项目一点点总结而来!)

文章目录 常用命令**apt相关****ls**&#xff1a;**cd****cp****ls -l | grep ssh**&#xff1a;会列出当前目录中包含 “ssh” 的文件或目录的详细信息。**系统资源**linux路由相关抓包工具和命令tcpdumpwiresharktshark iperf 常用命令 通过上下方向键 ↑ ↓ 来调取过往执行过…

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居&#xff1f; 邻居指的是在网络拓扑结构中与某一节点&#xff08;如路由器&#xff09;直接相连的其他节点。它们之间可以直接进行通信和数据交互&#xff0c;能互相交换路由信息等&#xff0c;以实现网络中的数据转发和路径选择等功能。&am…

[c语言日寄]字符串的左旋与右旋

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

基于单片机的开关电源设计(论文+源码)

本次基于单片机的开关电源节能控制系统的设计中&#xff0c;在功能上设计如下&#xff1a; &#xff08;1&#xff09;系统输入220V&#xff1b; &#xff08;2&#xff09;系统.输出0-12V可调&#xff0c;步进0.1V; &#xff08;3&#xff09;LCD液晶显示实时电压&#xff…

日常知识点之遗留问题梳理(被问到用uml画设计模式)

好多年不接触uml了&#xff0c;有一天面试&#xff0c;让用uml画出设计模式&#xff0c; 已经对uml的概念很模糊&#xff0c;隐约记得就是用例图&#xff0c;类图之类的&#xff0c;后面确定后&#xff0c;就是类图&#xff0c;用例图&#xff0c;时序图&#xff0c;都属于uml…

索引以及索引底层数据结构

一、什么是索引&#xff1f; 索引&#xff08;index&#xff09;是数据库高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff08;B树&#xff09;&#xff0c;这些数据结构以某种方式指向真在…

搭建Deepseek推理服务

概述&#xff1a; 本文介绍用Open webui ollama搭建一套Deepseek推理服务&#xff0c;可以在web页面上直接进行对话。作为体验搭建的是Deepseek 7b参数版本 首先选择一个云厂商创建一台ubuntu系统的虚拟机&#xff0c;带公网IP&#xff0c;通过shell登录虚拟机完成以下操作&…

如何在C++中使用YOLO模型进行目标检测

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

【第10章:自然语言处理高级应用—10.4 NLP领域的前沿技术与未来趋势】

各位技术探险家们,今天我们要开启一场穿越语言智能奇点的时空之旅。从正在改写物理定律的万亿参数大模型,到能看懂《星际穿越》剧本的跨模态AI,再到正在颠覆编程方式的神经-符号混合系统……这篇万字长文将带你摸清NLP技术进化的七块关键拼图。(建议边读边做笔记,文末有技…

SpringBoot+微信小程序+数据可视化的宠物到家喂宠服务(程序+论文+讲解+安装+调试+售后等)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在经济高速发展、物质生活极大丰富的当下&#xff0c;人们的精神需求愈发凸显&#xff0…

案例-06.部门管理-根据ID查询

一.根据ID查询-接口文档 二.根据ID查询-Controller层 package com.gjw.controller;/*** 部门管理Controller*/import com.gjw.anno.Log; import com.gjw.pojo.Dept; import com.gjw.pojo.Result; import com.gjw.service.DeptService; import com.gjw.service.impl.DeptServi…

C++17中的LegacyContiguousIterator(连续迭代器)

文章目录 特点内存连续性与指针的兼容性更高的性能 适用场景与C接口交互高性能计算 支持连续迭代器的容器示例代码性能优势缓存局部性指针算术优化 注意事项总结 在C17标准里&#xff0c;LegacyContiguousIterator&#xff08;连续迭代器&#xff09;是一类特殊的迭代器。它不仅…

【Kubernetes】k8s 部署指南

1. k8s 入门 1.1 k8s 简介 需要最需要明确的就是&#xff1a;kubernetes&#xff08;简称 k8s &#xff09; 是一个 容器编排平台 &#xff0c;换句话说就是用来管理容器的&#xff0c;相信学过 Docker 的小伙伴对于容器这个概念并不陌生&#xff0c;打个比方&#xff1a;容器…

Redis 03章——10大数据类型概述

一、which10 &#xff08;1&#xff09;一图 &#xff08;2&#xff09;提前声明 这里说的数据类型是value的数据类型&#xff0c;key的类型都是字符串 官网&#xff1a;Understand Redis data types | Docs &#xff08;3&#xff09;分别是 1.3.1redis字符串&#xff0…