一、JVM参数的使用
1、Xms与Xmx
Xms:JVM启动时初始化堆内存的大小
Xmx:JVM分配的堆内存的最大值
Xms设置的值过小,可能会导致应用启动时内存不够,从而应用启动失败,Xmx值过小,可能会导致应用启动后运行一段时间,内存不够用,一般设置Xmx大小为总机器内存的80%。同时将Xms的值和Xmx的值设置为一样,从而减少系统新增heap内存带来的性能损耗。
错误:java.lang.OutOfMemoryError. Java heap space
JVM在启动的时候会自动设置JVM Heap的值,Heap的大小是Young Generation 和Tenured Generaion 之和。 在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息。此时分2种情况,因为某个JVM线程申请不到足够的一块大内存而抛出异常,JVM进程内其它线程能够正常访问,只是当前线程OOM;另外一种是某个JVM线程连很小内存都申请不到,所有堆内存都被用光,进行full gc都不能回收内存,此时会导致整个JVM进程退出,java.lang.OutOfMemoryError: GC overhead limit exceeded就是这种情况,可以使用-XX:-UseGCOverheadLimit
去掉JVM默认的参数设定,但最后JVM还是因为OOM会退出。此时可以通过Thread.setDefaultUncaughtExceptionHandler()方法来保留当前JVM线程退出前的未捕捉到的错误信息到指定文件(保留现场),但是当前JVM线程必定会因为抛出的error而退出的。当所有非守护线程都退出时,JVM就会退出!
解决方法:修改JVM Heap的设置即Xms Xmx
2、XX:PermSize与XX:MaxPermSize
这2个参数分别是持久代的初始化大小和最大值,是用来存放加载的class及meta元素,如果应用加载的类较多,则需要调大一些。
错误:java.lang.OutOfMemoryError. Java PermGen space
sun的GC不会在主程序运行期对PermGen space进行清理,所以如果你的APP会载入很多CLASS的话,就很可能出现PermGen space溢出。
解决方法:修改XX:MaxPermSize的大小
3、Xss
这个参数表示分配给每个线程的栈的大小,JDK5.0以后默认都是分配1M的空间,一般这个值足够用了
错误:java.lang.StackOverFlowError
通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常是程序的递归层次太多,或者出现了死循环(笔者就遇到过),才会报这个错。
解决办法:可以先试着把Xss的值调到2M,如果还没有解决问题就要修改程序了。如果不容易查出问题所在,可以dump线程下来分析,本文后面会介绍。
4、需要注意的是,当线程栈所需要的空间申请不到,也就是说内存不够时,也会报out of memory的错误。还有一种情况不管是申请堆空间还是申请栈空间,当空间不够需要抛出异常,而保存异常信息也是需要空间的,所以当此时不够空间来收集异常信息,此时的JVM就会crash。这是一个临界的状况,需要注意。
以上是常用的几个JVM参数,其它的参数需要了解 可以参考:
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
二、JVM性能监控
1、JVM自带的常用命令
JPS:查看机器上的JVM进程,也可以指定ip,进行远程连接,但必须要有权限,默认端口1099
该命令会显示JVM进程的id和名字
Jstat :查看具体某个JVM进程的具体状态
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
解释如下:
Option 包括以下选项:
-class
-compiler
-gc
-gccapacity
-gccause
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcpermcapacity
-gcutil
-printcompilation
Vmid 就是 jps 查看到的进程 id ,如 Jserver 的进程 id 是 15117 。
Interval 是时间间隔,单位为毫秒, 1000 就是一秒。
Count 就是需要查看的次数。
例子假设我们需要查看 172.25.1.24 机器 vmid 为 15117 的 gc 的情况,可以输入下面的命令:
jstat -gc 15117@172.25.1.24 1000 3
jstack:linux特有,查看某个线程的具体情况
jmap:打印出某个java进程(使用pid)内存内的所有‘对象’的情况(如:产生那些对象,及其数量)。
用法:
jmap [ option ] pid
jmap [ option ] executable core
jmap [ option ] [server-id@]remote-hostname-or-IP
常用参数:
-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
-finalizerinfo 打印正等候回收的对象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
示例:jmap -dump:format=b,file=test.bin 4939
一般dump下来的数据需要使用工具查看,可参考:http://blog.csdn.net/fenglibing/article/details/6298326
也可使用visualVM分析JVM内存
visualVM的JVM远程连接机器监控VM
visualVM在JDK6.0 update 7 中自带,java启动时不需要特定参数,监控工具在bin/jvisualvm.exe),能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的。
需要配置如下:
JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true -Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=
-Djava.rmi.server.hostname=$HOST_NAME"
上述配置了JMX远程连接的端口、需要认证、指定用户和密码存储的文件
然后从指定的文件中找到用户名和密码,然后打开visulVM远程连接该机器
三、GC分析
先看一下HotSpot VM的对内存结构如下:
java中新建的对象都是用新生代分配内存,新生代由Eden和Survivor组成。Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。旧生代用于存放新生代中经过多次垃圾回收仍然存活的对象。每次jvm垃圾回收会将新生代的无用内存回收,如果gc前后heap内存回收总量比新生代回收的总量要少,说明有内存被提升到老年代和永久区中,通常以此依据来分析内存泄露问题。
新生代GC一般是使用复制算法进行清理,因此按照复制算法的原理将新生代分成了3个区域:Eden、Survivor0、Survivor1。Hotspot虚拟机的3个空间缺省配比为:8:1:1,jvm只会使用eden和1个survivor作为新生代空间.当新生代空间不足时发生minor gc,此时根据复制算法, jvm会首先将eden和from survivor中存活的对象拷贝到to survior中,然后释放eden和from中的所有需要回收对象,最后调换from/to survior,jvm将eden和新的from survior作为新生代。当然上述minor gc顺利执行还取决于很多因素,这里只描述了最理想化的状态。
下面举例说明:
上面截图的是连续2次一般的gc,一旦eden区已满(gc发生频繁内存泄露,从而导致增加内存补充eden区都不够用),或者Permanet Generation满 了都会发生full gc,而full gc的时候JVM要暂停所有进程,所以频繁full gc是绝对不允许出现在生成环境的。
先看上图第一次gc:1642004K->4067K(1843200K:指的是eden区+S0), 0.0041120 secs] 1750731K->112796K(3989504K:young+old), 0.0043270 secs]
表示eden区及S0从1642004K到回收后4067K,总共大小1843200K,回收了1637937K(eden+S0被回收的空间)的空间,heap从1750731K到112796K,总共大小3989504K,回收了1637953K(eden+S0+S1被回收的空间),说明有16K的对象(来自S0区)被升到tenure区或perm区了。
同样看接下来的一次gc: 1642467K->4342K(1843200K), 0.0066650 secs] 1751196K>113086K(3989504K), 0.0069010 secs]
这次eden区回收了1638125K, heap回收了1638110K,有15K数据被提升了。
虽然每次都有10多K的数据被提升了但是它的gc时间和heap的总大小并没有变化,所以暂时还没有什么内存泄露。上述是单点的分析,当怀疑系统内存泄露时不仅要单点分析还需要进行周期性观察jvm的gc和full gc的情况,类似上面的分析过程一样。
不能仅仅凭短时间的几个抽样比较,因为对于抽样来说,Full GC前后的区别,运行时长的区别,资源瞬时占用的区别都会影响判断。同时要结合Full GC发生的时间周期,每一次GC收集所耗费的时间作为辅助判断标准。
顺便说一下,Heap的 YoungGen,OldGen,PermGen的设置也是需要注意的,并不是越大越好,越大执行收集的时间越久,但是可能执行Full GC的频率会比较低,因此需要权衡。这些仔细的去了解一下GC的基础设计思想会更有帮助,不过一般用默认的也不错。还有就是可以配置一些特殊的GC,并行,同步等等,充分利用多CPU的资源。
四、内存泄露
程序中要注意的,经常可能出现内存泄露的地方:
本地内存的占用包括这么几种:
- java.nio.ByteBuffer#allocateDirect 使用Unsafe操作本地内存的DirectByteBuffer
- java.nio.channels.FileChannel#map 它实际上也是返回的DirectByteBuffer实例
- 通过JNI调用c/c++等代码,主要指native方法
- jvm进程本身消耗的内存
- 依赖的一些so库(linux上)使用的内存
DirectByteBuffer
这个占用本地内存的大小可以通过著名的MaxDirectMemorySize进行限制。其原理可以通过翻看java.nio.DirectByteBuffer#DirectByteBuffer(int) 构造函数得知。 因此也是最容易排除掉的一种泄露
另外,D.B.B 默认是通过PhantomReference机制释放内存的
可以简单的理解为,java.nio包里对本地内存的常规使用,都是可以通过MaxDirectMemorySize进行限制的。 所谓常规使用,是相对于通过反射访问一些package/private 权限的API的使用方式而言的。 后面会稍微提到下这种方式。
native方法泄露
其实D.B.B 最终也是通过native方法操作本地内存,但是因为可以通过M.D.M.S 参数限制,因此单独作为一种场景来讲。native方法泄露一般是指malloc了但是没有正确的free的场景。
这里出镜率最高的恐怕是 Deflater/Inflater 了。起初排查这个东西,只是照着葫芦画瓢不大理解其中原理,葫芦在这(http://bluedavy.me/?p=205)。其实严格来说,它只是回收不及时,不能算是真正的泄露,因为Xflater实现了finalize方法,借助jvm的Finalizer机制回收内存。但是Finalizer在高并发的情况下,容易回收不及时,只有一个最多两个线程呼哧呼哧的做finalize调用,(http://www.oracle.com/technetwork/java/javamail/finalization-137655.html)Oracle官方这篇文章给出了如何尽量降低、避免finalize内存滞留问题的详细讨论。native方法导致泄露这种场景的通用招式就是,使用googleperf/扁鹊之类的工具追踪记录内存分配情况
简单介绍下gperf的使用方法:
- 下载http://download.savannah.gnu.org/releases/libunwind/libunwind-0.99-beta.tar.gz , configure;make;sudo make install
- 下载http://google-perftools.googlecode.com/files/google-perftools-1.8.1.tar.gz ,configure --prefix=/home/admin/perftools;make;sudo make install
- 在应用程序启动前加入:
export LD_PRELOAD=/home/admin/perftools/lib/libtcmalloc.so
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
export HEAPPROFILE=/home/admin/perftools/test
前两个是linux的动态库机制,具体含义不展开,可以谷歌之。第三个是googleperf使用的环境变量,用来定义吐出的文件的路径前缀。可以将上述三个export放到启动脚本里,这样可以避免bash环境变量的集成问题
运行一段时间后,会在/home/admin/perftools目录下生成test打头的文件: test.0001.heap/test.0002.heap,也许还有test_.0001.heap ...,后者推断是有子进程的情况下生成的。
使用perftools/bin/pprof 工具可以对生成的heap数据进行各种姿势的分析,最基本的产出内存占比:
% pprof --text gfs_master /tmp/profile.0100.heap
255.6 24.7% 24.7% 255.6 24.7% GFS_MasterChunk::AddServer
184.6 17.8% 42.5% 298.8 28.8% GFS_MasterChunkTable::Create
176.2 17.0% 59.5% 729.9 70.5% GFS_MasterChunkTable::UpdateState
169.8 16.4% 75.9% 169.8 16.4% PendingClone::PendingClone
76.3 7.4% 83.3% 76.3 7.4% __default_alloc_template::_S_chunk_alloc
49.5 4.8% 88.0% 49.5 4.8% hashtable::resize
...
The first column contains the direct memory use in MB.
The fourth column contains memory use by the procedure and all of its callees.
The second and fifth columns are just percentage representations of the numbers in the first and fourth columns.
The third column is a cumulative sum of the second column (i.e., the kth entry in the third column is the sum of the first k entries in the second column.)
那么,通过对第四列排序,就得到了一个调用链上某方法及其被调用方法的内存占用情况。
实际中,更典型的结果可能类似这种:
0.0 0.0% 100.0% 6.4 5.4% 0x00007f6c29dfc4e6
0.0 0.0% 100.0% 6.4 5.4% 0x00007f6c29e0370f
0.0 0.0% 100.0% 6.9 5.9% JavaCalls::call_helper
0.0 0.0% 100.0% 46.9 39.6% ArrayAllocator::allocate
0.0 0.0% 100.0% 46.9 39.7% universe_post_init
0.0 0.0% 100.0% 48.6 41.1% init_globals
0.0 0.0% 100.0% 48.8 41.3% JNI_CreateJavaVM
0.0 0.0% 100.0% 48.8 41.3% Threads::create_vm
0.0 0.0% 100.0% 51.9 43.9% 0x00007f6c2a5d6608
0.0 0.0% 100.0% 51.9 43.9% Java_java_lang_UNIXProcess_forkAndExec
0.0 0.0% 100.0% 54.1 45.7% __clone
0.0 0.0% 100.0% 54.2 45.8% start_thread
0.0 0.0% 100.0% 67.9 57.4% AllocateHeap
109.7 92.8% 92.8% 109.7 92.8% os::malloc
除了C/C++函数,还会看到一些0x地址。。。。这是什么鸟,怎么看? 答案是暂时没法看!JVM的同学正在做功能,不久的明天就能从地址映射到函数名称了。
但是, 联想到JNI在java 函数声明和C/C++名称上是有修饰规则的:
Java_package_Class_method
有了这个,就能定位到对应的java方法了。 再明确一下,gperf trace到的内存信息能看到某方法本身及其调用的方法占用的虚拟内存之和(第4列)。
上图是个真实的案例,非常缓慢的内存泄露,两周天600m的样子。然后对比分析一段时间跨度的test.xxx.heap ,发先0x00007f6c2a5d6608这个函数消耗一直在涨,关键点在于它相邻的的Java_java_lang_UNIXProcess_forkAndExec,占用大小、变化情况是那么的相似。。。按照java native修饰规则,这个对应 java.lang.UNIXProcess.forkAndExec 方法,那么这样就回到了我们熟悉的Java 领域,具体到这个案例,考虑到创建进程这么有特点的功能,一问开发就知道了,是调用了shell,读取系统的文件参数,并且每分钟80多下,也就是80多个进程,两周这个数量也是蛮喜人的。。。于是元凶算是抓到了
依赖的so库
这个也是一个真实线上场景的案例。这个只简单描述下,开下脑洞,细节数据当时犯懒也没记录下来。应用使用了Inflater压缩数据,反复创建,并发度较高。 前面也提到,Inflater/Deflater在未及时调用end的是时候会导致内存泄露,但是这个经过初步改造之后,已经都加上了end的调用,理论上不应该再有滞留问题,并且通过jmap -histo:live 也证实Finalizer都已执行完毕,btrace跟踪方法, 与end方法调用数量相等,进一步证明没有未调用end的情况。但是rss依然居高不下,这时楼主就没辙了,然后咨询了 @云达 , 据说之前发现过malloc释放内存不及时的情况,将jvm的依赖的malloc库替换成jemalloc后问题解决。 然后才知道了linux的malloc如今已经不是简单的分配、释放内存,也有一套复杂的机制。。。 这里也是产生了碎片问题,这篇文章描述了这个问题:malloc的内存用free释放后为何系统回收不了。 博主也是个linux外行,这里不做展开,仅作为一种实际存在的case,开下脑洞,细节自己挖:)
JVM自身bug
JVM自身的bug也是五花八门的,只是想到了这种可能,或者其他文档中有提到这种可能,还没有处理过实际案例。
相关推荐
调优主要涉及选择合适的垃圾收集器,调整堆大小和新生代比例,以及设置GC日志,通过监控GC行为来优化性能。 总结,理解并掌握JVM参数和GC机制是Java开发中的重要技能。通过合理配置JVM参数,我们可以有效控制内存...
JVM参数设置是优化Java应用性能的关键环节,它可以帮助我们控制JVM的行为,如内存分配、垃圾回收策略、线程调度等。下面将详细介绍一些重要的JVM参数及其作用。 1. 内存设置: - `-Xms` 和 `-Xmx`:这两个参数用于...
- **监控与调整JVM参数**:根据实际运行情况动态调整,如调整新生代和老年代的大小。 7. **JVM诊断工具**: - `jmap`:获取堆内存快照,分析内存分配。 - `jhat`:分析`jmap`导出的内存转储文件。 - `jstack`:...
### JVM参数设置详解 在Java应用开发与维护过程中,JVM(Java虚拟机)的配置至关重要,它直接影响到应用程序的性能表现与稳定性。本文将基于提供的文件内容,深入解析Linux环境下JVM的基本参数设置方法及原理。 ##...
- **监控远程应用**:连接成功后,远程JVM的监控功能与本地应用相同,可以进行性能监控、内存分析等操作。 5. **使用插件扩展功能** VisualVM的插件中心提供许多扩展,如JProfiler、NetBeans Profiler等,可以...
5. **GC调优**:包括如何分析GC日志,理解GC停顿(Stop-the-World)事件,以及如何通过调整JVM参数来改善系统性能,如设置堆大小、新生代与老年代的比例、存活代的晋升策略等。 6. **性能监控与诊断工具**:如...
"jvmgc日志分析工具"专为解析和可视化JVM生成的GC日志而设计,帮助开发者识别内存瓶颈,调整内存设置,以及诊断可能的性能问题。 GC日志是JVM在运行过程中记录的关于垃圾收集活动的详细信息,包括垃圾收集的起始...
理解并正确配置JVM参数对于优化应用程序性能、内存管理和故障排查至关重要。本文将深入探讨JVM的常用参数设置,以及它们如何影响Java应用程序的运行。 一、JVM内存设置 1. **堆内存**: - `-Xms`:初始堆大小,...
1. 在开发和测试环境下,可以通过配置JVM参数来输出GC日志。例如,添加`-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime`参数,可以详细记录GC事件,包括简要信息、...
JVM参数设置是Java应用程序优化的关键环节,直接影响到程序的性能和稳定性。下面将详细解释提供的JVM参数及其对性能的影响。 1. **堆大小设置**: - `-Xmx` 和 `-Xms` 用于设定JVM的最大堆(`Max Heap Size`)和最小...
在IT行业中,性能监控和调优是至关重要的环节,尤其对于服务器端应用如Tomcat、数据库系统MySQL和Oracle,以及Java虚拟机(JVM)来说。这些组件在支撑大规模业务时,性能表现直接影响到系统的响应速度和用户体验。...
《JVM性能调优——JVM内存整理及GC回收》是针对Java开发人员的重要主题,尤其是在大型企业级应用中,确保JVM(Java虚拟机)的高效运行是至关重要的。本资料深入探讨了如何通过调整JVM内存设置和优化垃圾回收机制来...
JVM参数设置对于调整应用程序的性能、优化资源利用和确保系统稳定性至关重要。本篇文章将深入探讨JVM参数设置的各个方面。 首先,JVM参数主要分为两大类:标准参数和非标准参数。标准参数以“-X”或“-XX:”开头,...
JVM参数调优是优化Java应用程序性能的关键环节,尤其是在服务器端的应用中,如Web服务器Resin。本实践案例中,作者分别尝试了三种不同的垃圾回收(GC)策略:串行回收、并行回收和并发回收,并针对每种策略提供了...
GCViewer通过读取这些日志,可以实时监控GC的执行情况,对于排查性能问题和调优有着重要的作用。 三、主要功能 1. **实时监控**:GCViewer能够实时更新GC日志中的信息,动态显示GC事件,使开发者能够及时发现异常...
5. **JVM调优**:通过调整JVM参数,可以控制GC的行为,例如设置堆大小、新生代和老年代的比例、GC策略等。常用的JVM参数有`-Xms`, `-Xmx`, `-Xmn`, `-XX:NewRatio`, `-XX:SurvivorRatio`, `-XX:+UseConcMarkSweepGC`...
本文将详细介绍一些常用的JVM参数设置,这些参数适用于线上关键业务系统,并且具有较高的通用性。 #### 二、学习资源推荐 1. **开源项目启动脚本**:参考成熟的开源项目如ElasticSearch和Cassandra的启动脚本可以...
Java相关工具,如JConsole、VisualVM、JProfiler等,可以帮助监控和分析JVM的运行状态,包括内存使用、垃圾收集、线程和CPU负载等,从而辅助进行调优工作。 为了优化JVM性能,还可以调整其他参数,如-Xss控制线程栈...
理解这些区域的分配和垃圾收集机制,可以帮助我们更好地调整JVM参数,以适应不同应用的需求。 在实际应用中,我们可以使用各种工具进行JVM堆栈性能分析,例如VisualVM、JProfiler、YourKit等。这些工具提供了丰富的...
5. **JVM配置调整**:JProfiler11还可以提供JVM参数建议,帮助开发者正确设置JVM初始堆大小、最大堆大小、内存池等关键参数,确保应用程序稳定运行。 在实际使用中,JProfiler11的详细报告和可视化界面使得问题定位...