jmap

2019/02/25 15:26
阅读数 13

java.lang.OutOfMemoryError: Java heap space

当应用程序申请更多的内存,而Java堆内存已经无法满足应用程序对内存的需要,就会抛出此种异常。

java.lang.OutOfMemoryError: PermGen space及其解决方法

表示Java永久带(方法区)空间不够,永久带用于存放类的字节码和长常量池,类的字节码加载后存放在这个区域,这和存放对象实例的堆区是不同的,大多数JVM的实现都不会对永久带进行垃圾回收,因此,只要类加载的过多就会出现这个问题。一般的应用程序都不会产生这个错误,然而,对于Web服务器来讲,会产生有大量的JSP,JSP在运行时被动态的编译成Java Servlet类,然后加载到方法区,因此,太多的JSP的Web工程可能产生这个异常。

java.lang.OutOfMemoryError: unable to create new native thread

创建了太多的线程,而能创建的线程数是有限制的,导致了这种异常的发生。

在Linux下线程使用轻量级进程实现的,因此线程的最大数量也是操作系统允许的进程的最大数量。

java.lang.OutOfMemoryError:GC overhead limit exceeded

是在并行或者并发回收器在GC回收时间过长、超过98%的时间用来做GC并且回收了不到2%的堆内存,然后抛出这种异常进行提前预警,用来避免内存过小造成应用不能正常工作。

java.lang.StackOverflowError ...

JVM的线程由于递归或者方法调用层次太多,占满了线程堆栈而导致的,线程堆栈默认大小为1M。

java.net.SocketException: Too many open files

由于系统对文件句柄的使用是有限制的,而某个应用程序使用的文件句柄超过了这个限制,就会导致这个问题。

 

工具使用:

jmap命令(Java Memory Map)

map命令可以获得运行中的jvm的堆的快照,从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小等等。可以使用jmap生成Heap Dump。 

JVM Memory Map命令用于生成heap dump文件,如果不使用这个命令,还可以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候自动生成dump文件。 jmap不仅能生成dump文件,还可以查询finalize执行队列、Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

 

 

常用命令:

-dump

dump堆到文件,format指定输出格式,live指明是活着的对象,file指定文件名,:live是可选的。

[root@localhost jdk1.7.0_79] # jmap -dump:live,format=b,file=dump.hprof 24971
Dumping heap to /usr/local/java/jdk1 .7.0_79 /dump .hprof ...
Heap dump file created

可以将24971进程的内存heap以二进制形式(format=b)输出出来到dump.hprof文件里,再配合MAT(内存分析工具)。

-heap

打印heap的概要信息,GC使用的算法,heap的配置及使用情况,可以用此来判断内存目前的使用情况以及垃圾回收情况
 
 
 
 
[ci@localhost apps]$ jmap -heap 8633
Attaching to process ID 8633, please wait...   
Debugger attached successfully.
Server compiler detected.
JVM version is 25.66-b17

using parallel threads in the new generation.  ##新生代采用的是并行线程处理方式
using thread-local object allocation.
Concurrent Mark-Sweep GC  ##同步并行垃圾回收

Heap Configuration: ##堆配置情况
   MinHeapFreeRatio         = 40  ##最小堆使用比例 (GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比), 则放大堆内存的预估最大值)
   MaxHeapFreeRatio         = 70  ##最大堆可用比例 (GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比),则收缩堆内存的预估最大值, 预估堆内存是堆大小动态调控的重要选项之一. 堆内存预估最大值一定小于或等于固定最大值(-Xmx指定的数值). 前者会根据使用情况动态调大或缩小, 以提高GC回收的效率)
   MaxHeapSize              = 1073741824 (1024.0MB)  ##最大堆空间大小 (即-Xmx, 堆内存大小的上限)
   NewSize                  = 89456640 (85.3125MB)  ##新生代分配大小(新生代预估堆内存占用的默认值)
   MaxNewSize               = 348913664 (332.75MB)  ##最大可新生代分配大小(新生代占整个堆内存的最大值)
   OldSize                  = 178978816 (170.6875MB) ##老年代大小(老年代的默认大小, default size of the tenured generation)
   NewRatio                 = 2  ##新生代比例(老年代对比新生代的空间大小, 比如2代表老年代空间是新生代的两倍大小. The ratio of old generation to young generation.)
   SurvivorRatio            = 8  ##新生代与suvivor的比例(Eden/Survivor的值. 这个值的说明, 很多网上转载的都是错的. 8表示Survivor:Eden=1:8, 因为survivor区有2个, 所以Eden的占比为8/10. Ratio of eden/survivor space size. -XX:SurvivorRatio=6 sets the ratio between each survivor space and eden to be 1:6, each survivor space will be one eighth of the young generation.

   MetaspaceSize            = 21807104 (20.796875MB)  ##JDK1.8的方法区大小(分配给类元数据空间的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark ). 此值为估计值. MetaspaceSize设置得过大会延长垃圾回收时间. 垃圾回收过后, 引起下一次垃圾回收的类元数据空间的大小可能会变大)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)(类指针压缩空间大小, 默认为1G)
   MaxMetaspaceSize         = 17592186044415 MB   ##JDK1.8的方法区最大(是分配给类元数据空间的最大值, 超过此值就会触发Full GC. 此值仅受限于系统内存的大小, JVM会动态地改变此值)
   G1HeapRegionSize         = 0 (0.0MB)(G1区块的大小, 取值为1M至32M. 其取值是要根据最小Heap大小划分出2048个区块. With G1 the Java heap is subdivided into uniformly sized regions. This sets the size of the individual sub-divisions. The default value of this parameter is determined ergonomically based upon heap size. The minimum value is 1Mb and the maximum value is 32Mb. Sets the size of a G1 region. The value will be a power of two and can range from 1MB to 32MB. The goal is to have around 2048 regions based on the minimum Java heap size.)

Heap Usage:
New Generation (Eden + 1 Survivor Space):  ##新生代(伊甸区 + survior空间)
   capacity = 80543744 (76.8125MB)
   used     = 34725560 (33.11687469482422MB)  ##已经使用大小
   free     = 45818184 (43.69562530517578MB)  ##剩余容量
   43.11391335371745% used
Eden Space:   
   capacity = 71630848 (68.3125MB)
   used     = 29683880 (28.308753967285156MB)
   free     = 41946968 (40.003746032714844MB)
   41.44007900060041% used
From Space:
   capacity = 8912896 (8.5MB)
   used     = 5041680 (4.8081207275390625MB)
   free     = 3871216 (3.6918792724609375MB)
   56.56612620634191% used
To Space:
   capacity = 8912896 (8.5MB)
   used     = 0 (0.0MB)
   free     = 8912896 (8.5MB)
   0.0% used
concurrent mark-sweep generation:  ##老年代使用情况
   capacity = 178978816 (170.6875MB)
   used     = 134752632 (128.51012420654297MB)
   free     = 44226184 (42.17737579345703MB)
   75.28971026381133% used

36267 interned Strings occupying 3977048 bytes.
 
 
-finalizerinfo 
打印正等候回收的对象的信息
jmap -finalizerinfo pid

 

 
jmap -histo[:live] pid
 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.

 
 
 class name是对象类型,说明如下:
byte
char
double
float
int
long
boolean
[  数组,如[I表示 int []
[L+类名 其他对象
 
 
 
 
 
 
 
 
 
 
 
 
 

如果还需要看更加详细的信息,则使用:

jmap -dump:format=b,file=dumpFileName pid  直接生成在执行命令时所在的目录下面。

dump出来的文件可以用MAT、VisualVM等工具查看,也可以使用jhat。

jhat -port 9999 dumpFileName

如果dump出来的文件过大,可能需要指定Xmx(jhat实际启动了一个web应用)。

jhat -J-Xmx1000m -port 9999 dumpFileName

启动成功后,则可以通过浏览器查看:

ip:port

 
 
 
 
 

 

 

 

 

 

例如上图查询的是长度大于256的int数组。

 

jmap -histo:live 24971 | grep com.yuhuo 查询类名包含com.yuhuo的信息

jmap -histo:live 24971 | grep com.yuhuo > histo.txt 保存信息到histo.txt文件

 

-F 强迫.在pid没有响应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
-h | -help 打印辅助信息
-J<flag> 传递参数给jmap启动的jvm.
pid 需要被打印配相信息的java进程id,可以用jps查问

 

 

从安全点日志看,从Heap Dump开始,整个JVM都是停顿的,考虑到IO(虽是写到Page Cache,但或许会遇到background flush),几G的Heap可能产生几秒的停顿,在生产环境上执行时谨慎再谨慎。

 

live的选项,实际上是产生一次Full GC来保证只看还存活的对象。有时候也会故意不加live选项,看历史对象。

 

Dump出来的文件建议用JDK自带的VisualVM或Eclipse的MAT插件打开,对象的大小有两种统计方式:

 

  • 本身大小(Shallow Size):对象本来的大小。
  • 保留大小(Retained Size): 当前对象大小 + 当前对象直接或间接引用到的对象的大小总和。

 

看本身大小时,占大头的都是char[] ,byte[]之类的,没什么意思(用jmap -histo:live pid 看的也是本身大小)。所以需要关心的是保留大小比较大的对象,看谁在引用这些char[], byte[]。

 

(MAT能看的信息更多,但VisualVM胜在JVM自带,用法如下:命令行输入jvisualvm,文件->装入->堆Dump->检查 -> 查找20保留大小最大的对象,就会触发保留大小的计算,然后就可以类视图里浏览,按保留大小排序了)

 

 

 

 

 

转自:http://www.cnblogs.com/gaojk/articles/3886503.html

 

1、发现问题

 

1)、使用uptime命令查看CPU的Load情况,Load越高说明问题越严重;

 

2)、使用jstat查看FGC发生的频率及FGC所花费的时间,FGC发生的频率越快、花费的时间越高,问题越严重;

 

 

 

2、导出数据:在应用快要发生FGC的时候把堆导出来

 

1)、查看快要发生FGC使用命令:

 

jmap -heap <pid>

 

会看到如下图结果:

 

 

    以上截图包括了新生代、老年代及持久代的当前使用情况,如果不停的重复上面的命令,会看到这些数字的变化,变化越大说明系统存在问题的可能性越大,特别是被红色圈起来的老年代的变化情况。现在看到的这个值为使用率为99%或才快接近的时候,就立即可以执行导出堆栈的操作了。
    注:这是因为我这里没有在jvm参数中使用"-server"参数,也没有指定FGC的阀值,在线上的应用中通过会指定CMSInitiatingOccupancyFraction这个参数来指定当老年代使用了百分之多少的时候,通过CMS进行FGC,当然这个参数需要和这些参数一起使用“-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly”,CMSInitiatingOccupancyFraction的默认值是68,现在中文站线上的应用都是70,也就是说当老年代使用率真达到或者超过70%时,就会进行FGC。
    2)、将数据导出:
jmap -dump:format=b,file=heap.bin <pid>
这个时候会在当前目录以生成一个heap.bin这个二进制文件。
 
3、通过命令查看大对象
也是使用jmap的命令,只不过参数使用-histo
使用:jmap -histo <pid>|less
可得到如下包含对象序号、某个对象示例数、当前对象所占内存的大小、当前对象的全限定名,如下图:
查看对象数最多的对象,并按降序排序输出:

 

执行:jmap -histo <pid>|grep alibaba|sort -k 2 -g -r|less

 

结果如图:

 

 

查看占用内存最多的最象,并按降序排序输出:

 

执行:jmap -histo <pid>|grep alibaba|sort -k 3 -g -r|less

 

结果如图:

 

 


4、数据分析
这个时候将dump出的文件在ECLIPSE中打开,使用MAT进行分析(ECLIPSE需要先安装MAT插件),会展示如下截图:

 

可以从这个图看出这个类java.lang.ref.Finalizer占用500多M,表示这其中很多不能够被回对象的对象,此时点开hisgogram视图,并通过Retained Heap进行排序,如下截图:

 

 

从图中可以看出,被线线框圈起来的三个对象占用量非常大,那说明这几个大的对象并没有被释放,那现在就可以有针对性的从代码中去找这几个对象为什么没有被释放了。
再切换到dominator_tree视图:

 

这里可以看到velocity渲染也存在着问题,以及数据库的请求也比较多。

 

 

 

5、优化
优化的思路就是上面所列出来的问题,查看实现代码中所存在问题,具体问题具体分析。
 

总结

1.如果程序内存不足或者频繁GC,很有可能存在内存泄露情况,这时候就要借助Java堆Dump查看对象的情况。
2.要制作堆Dump可以直接使用jvm自带的jmap命令
3.可以先使用jmap -heap命令查看堆的使用情况,看一下各个堆空间的占用情况。
4.使用jmap -histo:[live]查看堆内存中的对象的情况。如果有大量对象在持续被引用,并没有被释放掉,那就产生了内存泄露,就要结合代码,把不用的对象释放掉。
5.也可以使用 jmap -dump:format=b,file=<fileName>命令将堆信息保存到一个文件中,再借助jhat命令查看详细内容
6.在内存出现泄露、溢出或者其它前提条件下,建议多dump几次内存,把内存文件进行编号归档,便于后续内存整理分析。

7.在用cms gc的情况下,执行jmap -heap有些时候会导致进程变T,因此强烈建议别执行这个命令,如果想获取内存目前每个区域的使用状况,可通过jstat -gc或jstat -gccapacity来拿到。

 

 

Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can’t attach to the process

在ubuntu中第一次使用jmap会报错:Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process,这是oracla文档中提到的一个bug:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7050524,解决方式如下:

  1. echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 该方法在下次重启前有效。

  2. 永久有效方法 sudo vi /etc/sysctl.d/10-ptrace.conf 编辑下面这行: kernel.yama.ptrace_scope = 1 修改为: kernel.yama.ptrace_scope = 0 重启系统,使修改生效。

 

 

写了一个OOM。

public class OOM {
public static void main(String[] args) {
List<StateMachine> list = new ArrayList<StateMachine>();
while(true){
list.add(new StateMachine());
}
}
}
使用如下配置:
-server
-Xms1024m
-Xmx1024m
-Xmn384m
-XX:+UseParallelOldGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/yp-tc-m-2777/Desktop/heap.bin

 

 

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部