文章目录
1. CPU蹦高
1.1 方案1
top
先查看cpu最高的
- 使用
ps -ef | grep
进程Id 或jps -l
看一下是怎样的后台程序
- 通过进程定位到具体的线程或者代码
ps -mp 进程Id -o THREAD,tid,time
参数解释:
-m
显示所有线程
-p
pid进程使用cpu时间
-o
该参数是用户自定义格式
- 1.4 将需要的线程Id转换成16进制格式(英文小写格式)
printf "%x\n" 线程Id
- 通过jstak工具查看线程栈帧情况
jstack 进程Id | grep tid(转换后的16进制线程id) -A60
-A60 代表前60行
1.2 方案2
top -c
再按 P 排序
用top -c 命令找出当前进程的运行列表,按一下 P 可以按照CPU使用率进行排序
- 通过进程找线程
top -Hp 进程Id
再按 P 排序
- 将线程Id转换成16进制
printf "%x\n" 线程Id
- 导出线程快照 ,看下里面情况
jstack -l 进程id > xxx.stack
- less 一下,然后进去根据 转换后的线程Id查看
2. OOM问题
除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。多数情况是老年代内存不足导致
2.1 常见的引起oom的问题原因:
- 一次性申请对象数量过多: 比如查询mysql数据库,一次性将几百万数据封装到list内,这个时候内存不足,会导致oom产生;
- 内存资源耗尽未释放: 找到未释放的对象进行释放;
- 本身资源不够: 堆内存分配不均,这个可以通过 jmp -heap 进程号 查询堆信息
2.2 排查oom问题思路
- 系统已经oom了,已经挂了;
- 这个没办法处理,只能提前设置:-XX:+HeapDumpOnOutOfMemoryError -XX:heapDumpPath=xxx
- 系统运行中还未oom
- 导出dump文件: jmp -dump -format=b,file=wang.hprof 进程编号
- 结合jprofile或者 jvisualvm 进行调试
- 查看最多跟业务有关对象–> 找到GcRoot–>查看线程栈信息
2.3 最常见的OOM情况有以下三种
java.lang.OutOfMemoryError: Java heap space
java堆内存溢出
此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
Java 堆溢出原因
-
无法在 Java 堆中分配对象
-
应用程序保存了无法被GC回收的对象。
-
应用程序过度使用 finalizer。
解决方案- 1.查找关键报错信息,如java.lang.OutOfMemoryError: Java heap space
- 2.使用内存映像分析工具(如Eclipsc Memory Analyzer或者Jprofiler)对Dump出来的堆储存快照进行分析,分析清楚是内存泄漏还是内存溢出。
- 3.如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,修复应用程序中的内存泄漏。
- 4.如果不存在泄漏,先检查代码是否有死循环,递归等,再考虑用 -Xmx 增加堆大小。
-
java.lang.OutOfMemoryError: PermGen space
java永久代溢出
即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,
使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。 -
java.lang.StackOverflowError
栈溢出
不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
栈溢出原因- 在单个线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出StackOverflowError 异常。
- 不断地建立线程的方式会导致内存溢出。
解决方案 - 查找关键报错信息,确定是StackOverflowError还是OutOfMemoryError
- 如果是StackOverflowError,检查代码是否递归调用方法等
- 如果是OutOfMemoryError,检查是否有死循环创建线程等,通过-Xss降低的每个线程栈大小的容量
其他情况:
-
java.lang.OutOfMemoryError: Direct buffer memory
本机直接内存溢出
ByteBuffer分配8MB直接内存,而JVM参数-XX:MaxDirectMemorySize=5M指定最大是5M,因此发生直接内存溢出。
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使用,而且也可能导致OOM。
在JDK1.4 中新加入了NIO(New Input/Output)类,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
直接内存溢出原因
本机直接内存的分配虽然不会受到Java 堆大小的限制,但是受到本机总内存大小限制。
直接内存由 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。
NIO程序中,使用ByteBuffer.allocteDirect(capability)分配的是直接内存,可能导致直接内存溢出。
解决方案- 检查代码是否恰当
- 检查JVM参数-Xmx,-XX:MaxDirectMemorySize 是否合理。
-
java.lang.OutOfMemoryError: GC overhead limit exceeded
这个是JDK6新加的错误类型,一般都是堆太小导致的。
Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。
解决方案- 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
- 检查JVM参数-Xmx -Xms是否合理
- dump内存,检查是否存在内存泄露,如果没有,加大内存。
-
java.lang.OutOfMemoryError-->Metaspace
方法区溢出
方法区,(又叫永久代,JDK8后,元空间替换了永久代),用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。
方法区溢出原因
使用CGLib生成了大量的代理类,导致方法区被撑爆
在Java7之前,频繁的错误使用String.intern方法
大量jsp和动态产生jsp
应用长时间运行,没有重启
解决方案- 检查是否永久代空间设置得过小
- 检查代码是否频繁错误得使用String.intern方法
- 检查是否跟jsp有关。
- 检查是否使用CGLib
2.4 方案1: jstack排查问题
使用top命令查看内存占比最高的进程
- 使用
top -Hp 进程Id
查看线程
- 使用 jstack打印栈信息 sudo jstack -F 927 > xxx.log
2.5 方案2: heap dump分析
要dump堆的内存镜像,可以采用如下两种方式:
-
设置JVM参数-XX:+HeapDumpOnOutOfMemoryError
设定当发生OOM时自动dump出堆信息。
不过该方法需要JDK5以上版本。-XX:HeapDumpPath=/Users/weihuaxiao/Desktop/dump/ 生成DUMP文件的路径 -
使用JDK自带的jmap命令
jmap -dump:format=b,file=heap.bin pid
pid指的是java进程Id heap.bin指的是文件名
dump堆内存信息后,需要对dump出的文件进行分析,从而找到OOM的原因。常用的工具有:
•mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具
。
•jhat
:JDK自带的java heap analyze tool,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言OQL,分析相关的应用后,可以通过http://localhost:7000来访问分析结果。不推荐使用,因为在实际的排查过程中,一般是先在生产环境 dump出文件来,然后拉到自己的开发机器上分析,所以,不如采用高级的分析工具比如前面的mat来的高效。
这样就启动起来了一个简易的HTTP服务,端口号是7000,尝试一下用浏览器访问一下它,本地的可以通过http://localhost:7000,就可以得到这样的页面:
Show instance counts for all classes (excluding platform) 平台外的所有对象信息
Show heap histogram 先是堆的统计信息