一、JVM常用参数介绍
堆内存相关
显式指定堆内存
-Xms<heap size>[unit] -Xmx<heap size>[unit]
-
heap size表示要初始化内存的具体大小
-
unit表示要初始化内存的单位。单位为g, m, k
举例:如果要为JVM分配最小2GB和最大5GB的堆内存大小:
-Xms2G -Xmx5G
显示指定新生代内存
默认情况下,YG的最小大小为1310MB,最大大小为无限制
共有两种指定新生代内存的方法:
1.通过-XX:NewSize和-XX:MaxNewSize指定:
-XX:NewSize=<young size>[unit] -XX:MaxNewSize=<young size>[unit]
举例:如果我们要为新生代分配最小256m和最大1024m的内存:
-XX:NewSize=256m -XX:MaxNewSize=1024m
2.通过-Xmn<young size>[unit]指定
-Xmn<young size>[unit]
举例:如果我们要为新生代分配256m(NewSize和MaxNewSize设为一致):
-Xmn256m
GC调优的一条经验总结是:
由于Full GC的成本远高于Minor GC,因此尽可能将对象分配在新生代是明确的做法,实际项目中根据GC日志分析新生代空间大小分配是否合理,适当通过-Xmn命令调节新生代的大小,最大限度降低新对象直接进入老年代的情况。
3.通过-XX:NewRatio=<int>设置老年代和新生代内存的比值
-XX:NewRatio=<int>
举例:如果我们要设置老年代和新生代内存比值为1。也就是新生代:老年代=1:1,新生代占整个堆的一半:
-XX:NewRatio=1
显式指定永久代/元空间的大小
JDK1.8之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小
-XX:PerSize=N #方法区(永久代)初始大小 -XX:MaxPermSize=N #方法区(永久代)最大大小,超过这个值将会抛出OutOfMemoryError
相对而言,垃圾收集行为在这个区域很少出现,但并非“永久存在”。
JDK1.8之后,永久代被彻底移除了,取而代之的是元空间,使用的是本地内存,也就是非堆内存。如果我们没有显式指定Metaspace的大小,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
常用参数:
-XX:MetaspaceSize=N #设置Metaspace的初始(和最小)大小 -XX:MaxMetaspaceSize=N #设置Metaspace的最大大小
垃圾收集相关
垃圾收集器
JVM具有四种类型的GC实现:
-
串行垃圾收集器:Serial(标记-复制)配Serial Old(标记-整理),适用于客户端下的HotSpot
-
并行垃圾收集器:Parallel Scavenge(标记-复制)配Parallel Old(标记-整理),吞吐量优先
-
CMS垃圾收集器:ParNew(Serial多线程并行版本)配CMS(标记-清除),
-
G1垃圾收集器:G1,局部收集和基于Region的内存布局,目标是在延迟可控的情况下获得尽可能高的吞吐量,整体来看是标记整理,两个region之间是标记复制。
配置方式:
-XX:+UserSerialGC -XX:+UserParallelGC -XX:+UserParNewGC -XX:+UserG1GC
GC日志记录
配置环境时,一定会配打印GC日志的参数,便于分析GC相关问题:
# 必选 # 打印基本GC信息 -XX:+PrintGCDetails -XX:+PrintGCDateStamps # 打印对象分布 -XX:+PrintTenuringDistribution # 打印堆数据 -XX:+PrintHeapAtGC # 打印Reference处理信息 # 强引用/弱引用/软引用/虚引用/finalize相关的方法 -XX:+PrintReferenceGC # 打印STW时间 -XX:+PrintGCApplicationStoppedTime # 可选 # 打印safepoint信息,进入STW阶段之前,需要找到一个合适的safepoint -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 # GC日志输出的文件路径 -Xloggc:/path/to/gc-%t.log # 开启日志文件分割 -XX:+UserGCLogFileRotation # 最多分割几个文件,超过之后从头文件开始写 -XX:NumberOfGCLogFiles=14 # 每个文件上限大小,超过就会触发分割 -XX:GCLogFileSize=50M
处理OOM
当发生OOM时,设置了以下这些参数可以将堆内存转储到一个物理文件中,以后可以用来查找泄漏:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_pid<pid>.hprof -XX:OnOutOfMemoryError="< cmd args >;< cmd args >" -XX:+UserGCOverheadLimit
-
HeapDumpOnOutOfMemoryError 指示 JVM 在遇到 OutOfMemoryError 错误时将 heap 转储到物理文件中。
-
HeapDumpPath 表示要写入文件的路径; 可以给出任何文件名; 但是,如果 JVM 在名称中找到一个
<pid>
标记,则当前进程的进程 id 将附加到文件名中,并使用.hprof
格式 -
OnOutOfMemoryError 用于发出紧急命令,以便在内存不足的情况下执行; 应该在
cmd args
空间中使用适当的命令。例如,如果我们想在内存不足时重启服务器,我们可以设置参数:-XX:OnOutOfMemoryError="shutdown -r"
。 -
UseGCOverheadLimit 是一种策略,它限制在抛出 OutOfMemory 错误之前在 GC 中花费的 VM 时间的比例
其他
-
-server:启动JVM。默认使用64位JVM
-
-XX:+UserStringDeduplication:将重复String值减少为单个全局char[]数据来优化堆内存
-
等等等。。。。。。
二、JVM调优相关工具
jps:虚拟机进程状况工具
可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一ID
jstat:虚拟机统计信息监视工具
用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时的数据
jinfo:Java配置信息工具
实时查看和调整虚拟机的各项参数。使用jps命令的-v参数可以查看虚拟机启动时显示指定的参数列表,但如果想知道未被显式指定的系统默认值,除了找资料,只能使用jinfo的-flag选项进行查询了
jmap:Java内存映像工具
jmap命令用于生成堆转储快照。作用不仅仅是为了堆转储快照文件,它还可以查询finalize执行队列、Java堆和方法区的详细信息,如空间使用率、当前使用的是哪种收集器
jhat:虚拟机堆转储快照分析工具
JDK提供jhat命令与jmap搭配使用,来分析jmap生成的堆转储快照。jhat内置了一个微型的HTTP/WEB服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。
jstack:Java堆栈跟踪工具
jstack命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致长时间停顿的常见原因。
JDK基础工具:用于支持基本的程序创建和运行
三、案例分析
大内存硬件上的程序部署策略
两种方法:
1.通过一个单独的Java虚拟机实例来管理大量的Java堆内存
注意事项:
-
尽量减少Full GC次数 《= 老年代要相对稳定,应用中绝大多数对象要”朝生夕灭“
-
回收大块堆内存而导致的长时间停顿
-
大内存必须有64位Java虚拟机的支持
-
应用必须稳定,否则很难转储快照和分析
-
相同的程序在64位虚拟机消耗的内存一般比32位虚拟机要打,这是由于指针膨胀,以及数据类型对齐补白等因素导致的,可以开启压缩指针功能来缓解。
2.同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源
方法:在一台物理机器上启动多个服务器进程,为每个服务器进程分配不同端口,然后在前端搭建一个负载均衡器,以反向代理的方式来分配访问请求。使用无Session复制的亲和式集群
可能出现的问题:
-
节点竞争全局的资源,如I/O
-
很难最高效率地利用某些资源池
-
32位内存限制
-
大量使用本地缓存的应用造成内存浪费,可改为集中式缓存
具体解决方案:
调整为5个32位JDK的逻辑集群,每个进程按2GB内存计算,占用了10GB内存。另外建立一个Apache服务作为前端均衡代理作为访问门户。考虑到用户对响应速度比较关心,并且文档服务的主要压力集中在磁盘和内存访问,处理器资源敏感度较低,因此改为CMS收集器进行垃圾回收。
集群间同步导致的内存溢出
问题:使用亲和式集群、全局缓存,服务在正常运行很长时间后最近不定期出现多次的内存溢出问题
原因:信息发送前需要进行安全校验,需要经过全局过滤器,而发送的信息需要在内存保留,但”最后一次操作时间“需要送到所有节点,这就导致一个页面出现数次乃至数十次的请求,导致集群各个节点之间网络交互非常频繁。
解决方案:如果使用非集中式的集群缓存来同步的话,读操作可频繁,但不应当有过于频繁的写操作,会带来很大的网络同步开销。
堆外内存导致的溢出错误
问题:报OOM,设置堆内存无用,甚至设置得越大,异常抛出越频繁
原因:用了NIO=》直接内存,是排除在JVM运行时数据区内存管理之外的部分,但仍会占用操作系统的内存。
解决方案:其他类似的区域:
-
直接内存
-
线程堆栈:通过-Xss调整大小
-
Socket缓存区
-
JNI代码
-
虚拟机和垃圾收集器
外部命令导致系统缓慢
问题:请求响应时间很慢,但应用本身不占资源。
原因:用java的Runtime.getRuntime().exec()会fork一个新的进程来执行Shell脚本,执行脚本多了、进程就多了
解决方案:改为使用Java的API去获取这些信息。
服务器虚拟机进程崩溃
问题:虚拟机频繁自己停止
原因:与虚拟机交互的第三方应用经常出现异常,本地应用程序使用异步方式调用Web服务,但是由于本地和第三方服务速度不对等,时间越长就累积了越多Web服务没有调用完成,导致在等待的线程和Socket连接越来越多,最终超过了虚拟机的承受能力。
解决方案:通知第三方应用修复无法使用的接口,并将异步调用改为生产者/消费者模式的消息队列实现。
不恰当数据结构导致内存占用过大
问题:每十分钟要加载一个约80MB的数据文件到内存进行数据分析,这段时间里Minor GC会造成500毫秒的停顿。
原因:分析数据文件期间,新生代绝大部分对象依然是存活的。
解决方案:如果不修改程序,可以考虑直接将Survivor空间去掉,治标不治本。关键问题是HashMap结构来存储数据文件空间效率太低了。
由Windows虚拟内存导致的长时间停顿
问题:应用偶尔出现间隔约一分钟的时间完全无日志输出,处于停顿状态。=》发现是垃圾收集的问题
原因:发现从准备开始收集,到真正开始收集之间所消耗的时间占了绝大部分;除收集器日志外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源管理中显示的占用内存大幅度减小,但是虚拟内存没有变化,因此怀疑程序在最小化时它的工作内存被自动交换到磁盘的页面文件中了,这样发生垃圾收集时就有可能因为恢复页面文件的操作导致不正常的垃圾收集停顿。
解决方案:加入参数:-Dsun.awt.keepWorkingSetOnMinimize=true
由安全点导致长时间停顿
问题:HBase集群,读写压力大,发现垃圾收集停顿经常3s以上
原因:有的循环是可数循环(int类型作为循环计数),但单次循环时间就很长,HotSpot不会在循环中插入安全点,所以要是插安全点的时候刚好有线程在执行这样的循环,别的线程都要等它完成这些。
解决方案:int改long,对于不可数循环,HotSpot将会在长时间后放安全点。
Eclipse运行速度调优
-
升级JDK版本的性能变化以及兼容问题
-
编译时间和类加载时间的优化
-
调整内存设置控制垃圾收集频率
-
选择收集器降低延迟