JVM参数以及问题定位步骤小结

2 篇文章 0 订阅
本文总结了JVM参数,包括横杠、-X和-XX参数,强调了-XX:+HeapDumpOnOutOfMemoryError的重要性。讨论了内存问题的概念,如内存泄漏和内存溢出,以及各种类型的内存溢出错误及其解决方案。介绍了GC日志和内存溢出后的定位问题步骤,包括使用jstack、jmap等工具进行分析。
摘要由CSDN通过智能技术生成

一 java的参数

普通的-Xms最小heap内存,-Xmx最大heap内存都知道
所有详细参数,参考: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
版本需要自己去查找了

#设置一样大,而已减少内存空间抖动
java -Xmx200m -Xmx200

1. -横杠开头标志参数

#什么都不输入,可以查看标志参数,类似查看help一样
java

在这里插入图片描述
常见的 -version 查看版本, -verbose开启信息信息输出,-cp 指定main入口(如果打包没指定),-classpath指定类路径,–javaagent就是idea破解版指定代理等

2. -X是非标参数 (-Xms -Xmx就是在其中)

java -X

在这里插入图片描述

3. -XX是不稳定参数,每个版本不一定有和能用,所以不能简单的java -XX回车来查看所有参数(七百多个)

#针对bool类型参数设置格式 -XX:+参数 设置true开启功能  -XX:-设置false关闭功能
#注意这里Final是最后生效参数,还有对应启动默认参数 java -XX:+PrintFlagsInitial
java -XX:+PrintFlagsFinal
#或者grep 过滤查找 这里grep是区分大小写的
java -XX:+PrintFlagsFinal |grep Dump
#上面的打印,因为同样触发了 java -参数,打印出了好多帮助的列表信息,
#所以加 -version 即查看当前版本,也能减少不要的信息
java -XX:+PrintFlagsFinal -version |grep Dump

在这里插入图片描述
其中 -XX:+HeapDumpOnOutOfMemoryError 就是开启OOM内存溢出时自动dump,常用

4. 参数样例

#设定GC日志 %t是按时间命名部分,因为GC日志运行久了太大,XX:UseGCLogFileRotation 可以循环覆盖采集日志到文件里(就是覆盖,可能会看到日志怪怪的,所以带上时间戳),
#按XX:GCLogFileSize大小切割,最多XX:NumberOfGCLogFiles个log
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
#也可以设定当有OOM时,dump文件下来


-XX:-DoEscapeAnalysis 不逃逸分析
-XX:-EliminateAllocations 不标用替换
-XX:-UseTLAB 去掉TLAB(ThreadLocalAllocationBuff)
-XX:MaxTenuringThreshold=15 设定对象经历多少次YGC(年轻代垃圾回收)后进入老年代,最大值15,在class头文件格式中仅有4bit表示次参数,最大也就是15,设置大了没用;paralle Scavenge和G1垃圾回收器默认是15,CMS垃圾回收器默认是6
-XX:TargetSurvivorRatio Survivor区目标存活率默认50%,指YGC时,survivor区经历年轻代1次2次3次...,累加到某年龄对象内存占用和大于年轻区总数的该阈值,就将该年龄及以上年龄的移动到老年代,不是指某年代的
-XX:+UseCMSCompactAtFullCollection 启用当FullGC时,如果时CMS垃圾回收,则进行内存整理(压缩),应对浮动垃圾Floating Garbage,如果不整理,那么碎片空间Memory Fragmentation导致新对象分配不下
-XX:CMSFullGCsBeforeCompation 默认0,指经过多少次FGC才进行上面参数的整理(压缩)
#出现Concurrent Mode Failure 和 Promotion Failed时,说明碎片太多,内存分配不下,CMS并发标记清除不行了,需要使用Serial Old这种垃圾回收器来 Stop-the-World来整理,可以通过降低下面阈值,尽量不触发这种行为
-XX:CMSInitiatingOccupancyFraction 90% r让CMS保持老年代足够的空间

-XX:+PrintCommandLineFlags
-XX:+PrintGC HelloGC #打印GC回收提示语句 可以自定义
PrintGCDetails PrintGCTimeStamps 打印GC详细信息
PrintGCCauses #打印GC产生原因
-XX:+UseConcMarkSweepGC 启用CMS垃圾回收器

-XX:StringTableSize 字符串常量池个数限制,总内存大小不受限制,1.8之前perm永久代可OOM,后metespace,受限系统内存


二 内存问题概念:

1. 内存泄漏 memory leak

有一块内存分配了,但无人占用,回收不了,别人是用不了

2. 内存溢出 outof memory

新产生对象多余回收的对象,最终空间不够分配

3 GC吞吐量与与吞吐率

1. 吞吐量:用户代码时间/(用户代码执行时间+GC时间)

Parallel Scavenge + Parallel Old就是追求吞吐量;数据分析处理类适合选择这种垃圾回收器,也是java8默认

2. 响应时间: STW(stop the world)的时间

CMS 就是追求 STW;网站用户连接、带界面响应的适合。

4. 内存溢出错误的几种原因类型与方案

【注意,不同版本java提示的类型不同,一般越新的版本提示的越详细,具体内容具体分析,下面是混杂进行记录的,后续在进行整理,也便于自己查找】

1. OutOfMemoryError:Javaheapspace

  • 原因:堆内存不够,无法分配更多内存,也可能是方法栈调用太深导致不够
  • 方案:heap dump,分析查看,检查是否静态容器、list无限加对象不释放,因业务数据变动等JVM中缓存数据太大没有用软引用等等,加内存,
#dump堆文件
-XX: +HeapDumpOnOutOfMemoryError

2. OutOfMemoryError: unable to create native thread

  • 原因:无法创建更多线程,超过系统配置的极限
  • 方案:检查系统限制线程数(文件句),如Linux默认允许单个进程可以创建的线程数是1024个;检查多线程代码块是否创建太多线程。
//查看限制个数
ulimit -u
//编辑修改 xx
vim /etc/security/limits.d/xx-nproc.conf

3. OutOfMemoryError: map failed

  • 原因:File MMAP(文件映射内存)时,系统内存不足异常
  • 方案:增加系统内存,减少每次mmap文件大小
#查看系统源码
Windows:FileDispatcherImpl.c
Linux:FileDispatcherImpl.c

4. OutOfMemoryError: Requested array size exceeds VM limit

  • 原因:申请的数组大小超过堆内存限制
  • 方案:

5. OutOfMemoryError: Metaspace

  • 原因:元数据占用空间超过限制;class数据 包含meta space与class space
  • 方案:jmap -clstats 命令查看class数据状态,或者jcmd GC.class_stats 命令查看(需启动参数: -XX:+UnlockDiagnosticVMOptions)
jmap -clstats
jcmd GC.class_stats
	-XX:+UnlockDiagnosticVMOptions
InstBytes:实例占用大小
KlassBytes:类占用大小
annotations:注解占用大小
CpAll:常量池中占用大小
MethodCount:方法个数
Bytecodes:字节码大小
MethodAll:方法占用大小
ROAll:只读内存中内存占用
RWAll:读写内存中内存占用

6. OutOfMemoryError: Compressed class space

  • 原因:class space 内存溢出导致的
  • 方案:同上

7. OutOfMemoryError: reason stack_trace_with_native_method

  • 原因: JNI 调用中内存不足
  • 方案:检查JNI等调用底层方法过多的相关代码快,加系统内存

6. OutOfMemoryError:GCoverheadlimitexceeded

  • 原因:每次GC回收对象少,导致GC太频繁了,效率极低,根本没时间和空间去执行用户代码,内存不够,
  • 方案:加内存,检查代码递归调用深度和是否死锁不死放资源,数据消费不掉等
-XX:-UseGCOverheadLimit #限制内存

3. java.lang.OutOfMemoryError:PermGenspace

  • 原因:perm老年代(永久代)空间不够(默认64M),1.8之前的常量池、代码块可能会导致
  • 方案:加永久代内存,检查常量定义、静态代码快或动态加载class等代码
-XX:MaxPermSize=128m
-XXermSize=128m
4. OutOfMemoryError:Directbuffermemory
  • 原因:JNI如native本地方法还有NIO/nettty中的调用系统的直接内存(unsafe类 零拷贝),JVM是无法回收这些内存的,是底层操作系统或调的JNI底层的东西去管理内存,有的回收,有的是重复使用。可能是直接内存设置太小了
  • 方案:调大直接内存,检查使用直接内存的代码,XX:MaxDirectMemorySize 重新设定,该参数的默认值为 Xmx 的值减去1个 Survior 区的值;在发生这种异常时,一般通过 JMX 的java.nio.BufferPool.direct里面的属性去监控直接内存的变化以及使用(其实就是 BufferPoolMXBean ),来定位问题。
-XX:MaxDirectMemorySize=128m
5. java.lang.OutOfMemoryError:unabletocreatenewnativethread
  • 原因:Stack空间不足以创建额外线程,要么线程太多要么stack空间小
  • 方案:linux查看开启的文件句柄数量,检查开启多线程的代码块,由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;Heap和Stack空间的总量有限,调小heap也可以让Stack变大。
-Xss
-Xms -Xmx

6. java.lang.StackOverflowError

  • 原因:线程栈的溢出,调用栈过深,导致线程栈占用大小超过-Xss(或者是-XX:ThreadStackSize)的限制;
    大多递归死循环,或者递归调用栈太深,比如那种回调背压模型的框架,netty + reactor 这种,一般线程栈需要调大一点。
  • 方案:检查方法栈调用代码块,linux设置文件句柄数量,调整-Xss参数增加线程栈大小, -XX:ThreadStackSize线程栈空间调大
-XX:ThreadStackSize
#查看系统文件 总结起来就是,32 位的系统一般是 512k,64 为的是 1024k
windows:os_windows.cpp
linux:os_linux.cpp

三 GC日志

不同的垃圾回收器均有所不同,但大同小异

四 一般内存溢出或性能监控后定位问题步骤

以一个简单list无限添加对象例子来展示:

//java代码,放在java目录下
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class JVMOOMSample01 {
    public static List<Object> list=new LinkedList();

    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                list.add(new Byte[1024]);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        Scanner sc = new Scanner(System.in);
        String str= sc.nextLine();
        System.out.println(str);
        System.out.println("开始打爆JVM,1s中 加 10M");
        new Thread(()->{
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                list.add(new Byte[1024*1024]);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
        System.out.println("main over");
    }
}
#启动shell命令,固定最大最小12M,打印GC日志,开启日志滚动切割写入,最多5个日志文件,每个2k大小(后面PrintCommandLineFlags打印VM命令显示最小8k),打印详细信息,打印时间戳,打印GC原因,OOM自动dump,-cp 指定jar的执行main入口class
java -Xms12M -Xmx12M  -Xloggc:/tmp/test-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=2k -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintCommandLineFlags -cp java-base-1.0-SNAPSHOT.jar JVMOOMSample01

未OOM前:
在这里插入图片描述
随意输入单词,进入后续线程启动,OOM:
在这里插入图片描述

1. 一般是运维监控系统,发现系统的某项异常指标,或者错误日志告警

系统指标问题分类:cpu利用率、还是负载高,内存高不高,读写IO高,还是cpu等待IO高?
错误日志是什么,如果是OOM类,按上面的类型大体定位(一般系统不可用了,立即重启,并根据情况设定应用日志、GC日志)

2. 根据指标例如cpu(或memory)找出最高进程

top
在这里插入图片描述
此处有两个,是因为我后台还有各kafka的集群
所以太多java时,可以jps 查看java的进程,找到pid 21920:
在这里插入图片描述

3. 找出那个线程最高

top -Hp pid #这个pid是上面进程的pid,然后列出很多相关的线程的pid(linux一切皆文件,文件描述符代表pid)

top -Hp 21920

在这里插入图片描述
好几个,看cpu 利用最高 21929这个

4. 导出该线程的堆栈(简单暴力就是GC日志或DumpOnMemory命令也就上了),但线上重要系统尽量要不影响关键业务

jstack

jstack 21929

在这里插入图片描述
因为其实已经OOM了,死进程了,强制进入

jstack -F 21929

在这里插入图片描述
看到是DebuggerException,这个是实例程序已经异常了,运行的pid在/tmp/临时生成的文件消失了,无法看到该pid
所以看不到那个线程了
还是回头看启动父进程吧,至少活着呢:
在这里插入图片描述
可以看到它创建的好多子线程,其中包括正常无限等待状态的t1线程,而t2没了,因为它死了,耶稣也留不住
至于jstack 的dump,其实就是 jstack -F 查看而已
参考文章:
jstack dump日志文件详解

5. 查找具体方法栈消耗资源

jstack -m 21920 

在这里插入图片描述
告诉没有死锁,看样子可以判定线程是否有死锁等的东西

6. 工作线程还是GC线程

所以线程启动,给个名字,debug定位问题,也好确定线程

7. 开始GC日志、或手动dump或 溢出时自动dump

#GC日志文件
java -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause -cp xxxx.jar xxxxmain类

在这里插入图片描述

#手动dump
jmap -dump:file=自定义文件名 pidxxx

在这里插入图片描述

#溢出时自动dump
-XX:+HeapDumpOnOutOfMemoryError

8. 分析dump文件

jhat,jvisualvm,mat,jprofile,arthas 等等工具,
有的是自带的,有的是远程连接的,有的是图形界面(这种限于揩测试环境或dump文件拿到,dump以及分析不要影响关键业务,)
区分生产环境是否适用该工具
linux生产环境,可能就只能用jdk自带的工具了,开发测试环境图像画工具jvisulavm(自带) jprofile(商业) arthas(阿里开源生产级别)工具

先讲可视化的:
这种可视化的一般都是远程连接,当然不可视化也可以远程连接,
远程连接要启动相关JVM功能,一般用JMX
jar包启动时配置JVM命令如下

#启动远程机器ip(jar运行的),远程连接端口,是否用户密码验证,是否ssl加密传输
java -Djava.rmi.server.hostname=172.20.10.7 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

jconsole (window):
在这里插入图片描述
在这里插入图片描述
一般开发测试,用可视化工具就是jvisualvm了,商业付费软件大公司可能不能装,麻烦
jvisualvm:
在这里插入图片描述
上面是jstatd连接,可以使用jmx连接,(弄出来的远程中,有刚才添加的机器,右键添加)
在这里插入图片描述
在这里插入图片描述
有堆dump,也有自己手动区触发GC,查看状态变化
在这里插入图片描述
可以查看线程状态
在这里插入图片描述
可以查看cpu执行时间,各线程执行cpu时间
在这里插入图片描述
也可以查看内存中的实例个数(就和jmap -histor 一样):

在这里插入图片描述
但这样监控不太方便,而且生产中,也不会说给你开个远程持续观察,影响业务。
要在在线生产环境定位,要么dump
可视化的,直接把dump文件弄下来会有实例 的内容查看,也能执行OQL语句查看
在这里插入图片描述
最好生产命令dump
载入dump文件后,查看实例内容值:
在这里插入图片描述
这里有显示线程,以及异常退出线程,点击直达:
在这里插入图片描述
OOM
然后类查看实例数量在这里插入图片描述
双击其中的,进入到实例数,可以查看值:
在这里插入图片描述
可以执行OQL 语句,类sql:
在这里插入图片描述
比如我们生产上发现有一种解析类型再内存中特别多,后来发现是缓存不够了,没能释放,无法扩大内存情况下,改为软引用的三级缓存解决方案
再去点击,查看到具体的值是什么,再结合生产数据定位是那个业务数据过多,缓存或内存设计不合理等等。

后面生产的工具使用,另外再讲吧,写文章太累了

参考:
java15 OOM类型
https://zhuanlan.zhihu.com/p/342914934
jhat
https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值