JVM原理探究及调优方法论

此文目的

本文不准备从盘古开天地开始讲述JVM的种种,相关的文章网上太多了,大多也无非转来转去,连图都差不多。笔者只整理个提纲挈领的学习路线指南,并对自己学习过程中遇到的坑和容易混淆和忽视的地方作个总结。见识有限,欢迎甄错。

JVM内存模型

内存模型

内存区域划分有多个维度,相同区域在不同维度的名称并不一样。如下图所示
在这里插入图片描述
可以看到,survivor区被划分为了survivor0和survivor1两个区域,但是在讲MinorGC的原理时,我们又会说survvior to和survivor from两个区域。事实上,survivor0和survivor1是物理维度的划分,而survivor to和survivor from是逻辑维度的划分,在MinorGC的过程中,survivor0和survivor1交替担当to区和from区。
来仔细解释一下MinorGC的过程:
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。大致如下图所示:
在这里插入图片描述
在这里插入图片描述

方法区和永久代

这两个概念,很多时候都被当做是同一个概念。实际上,“方法区”是java虚拟机规范中对存放类信息,字段,方法,常量,静态变量,接口和常量池的内存区域的定义,而“永久代”则是HotSpot VM在1.8版本以前对于方法区的具体实现。由于java虚拟机规范并没有对方法区的具体实现作限制,所以HotSpot VM和JRocket VM对于方法区的实现都是不一样的,JRocket中就没有永久代的概念。而在1.8及1.8以后的版本中,HotSpot VM用"元空间"–metaspace来代替永久代,实现方法区。
这个变化带来的就是VM参数的变化,所有的PermGen都被替换成了MetaSpace。并且metaSpace不再使用堆内存,而是使用系统内存。但是该发生的OOM一样会发生。原因也基本都是加载到内存中的 class 数量太多或者体积太大。

GC

GC算法

GC算法和GC收集器也是两个维度的概念。
GC算法包括清除算法(也叫标记清除算法),复制算法,标记-整理算法。
不同垃圾收集器针对不同的内存区域,采用不同的GC算法。
具体介绍,网上相关资料很多,可以参考这篇文章 https://blog.csdn.net/xiaoping0915/article/details/69525632

垃圾收集器

垃圾收集器经历了从串行收集器到并行收集器,再到并发收集器的进化过程。这三者的区别如下图所示
在这里插入图片描述
串行和并行的区别比较容易理解,而CMS垃圾收集器的原理要注意的是,虽然它是并发收集器,但它的GC线程并不是完完全全地与应用的进程并发进行,它只是通过用两次短暂停来代替并行GC的一次长暂停,以期达到减少应用线程暂停的目的,详见CMS垃圾回收机制

不同版本默认使用的垃圾收集器以及支持开发者定制的垃圾收集器都是不一样的
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器G1
与此同时,通过设置JVM参数也可以自己选择垃圾收集器。如要开启G1垃圾回收器,可以用-XX:+UseG1GC,支持G1垃圾回收器的JDK最低版本为JDK 7u4。在用户自己选择垃圾收集器的时候,要注意JDK版本的问题。
笔者用表格的形式列出了新生代和老年代的GC收集器的常见搭配方案:
在这里插入图片描述

FullGC触发条件

频繁FullGC导致的stop the world的现象,会大大影响系统的稳定性。尽管一代又一代的垃圾收集器的优化,使得stop the world的时间越来越短,但是在大型应用中,还是避之不及。
出发FullGC的情况有以下几种:

1. System.gc()方法的调用
2. 老年代不足
3. 方法区不足
4. concurrent mode failure
 concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间       不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。
5. promotion failed

minor gc时年轻代的存活区空间不足而晋升老年代,老年代又空间不足而触发full gc
6. 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC)。

OOM的类型

通常情况下,JVM的GC机制能保证应用的正常运行,导致系统频繁FullGC的原因百分之九十都是内存溢出(OOM)。OOM分为以下几类:
7. Java.lang.OutOfMemeoryError:Java heap space 堆空间的内存溢出,可能的原因是某个可达性分析认为不能被回收的对象随着时间推移变得越来越大,例如某个static类型的map对象,被不停地塞入键值对,也可能是大循环或者死循环不断创建对象,而对象分配内存的速度超过了GC清理内存的速度。
8. Java.lang.OutOfMemeoryError:GC overhead limit exceeded 这种OOM异常是Hotspot VM 1.6定义的一个策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。” 那么为什么会出现这种GC效率低下的现象呢?通常是因为老年代内存占有过多导致的频繁GC,这种情况下,可以增加-verbose:gc -XX:+PrintGCDetails来分析具体原因,也可以加-XX:+HeapDumpOnOutOfMemoryError,这样OOM时会自动做Heap Dump,第二种方法适用于所有OOM异常的排查。
9. Java.lang.OutOfMemoryError: PermGen space(JAVA8引入了Metaspace区域)方法区内存溢出,通常是因为加载的类过多,可以先排除程序问题导致的重复类加载,或者加大方法区内存的分配
10. Java.lang.OutOfMemoryError: unable to create new native thread 产生这种异常的原因是由于系统在不停地创建大量的线程,且不进行释放。

JVM调优

JVM调优参数

正确设置JVM参数,可以尽可能多地避免系统资源浪费,尽可能详细地掌握系统运行情况,并且对可能出现的问题防患于未然。
Xms:堆初始空间
Xmx:堆最大空间
Xmn: 年轻代大小
XX:MaxNewSize 新生代最大空间 建议设置为整个堆的1/3到1/4
XX:NewSize
XX:MaxTenuringThreshold survivor中到老年代中的年龄阈值
Xss:每个线程的栈大小
java -XX:+PrintCommandLineFlags -version 得到JDK建议的内存分配大小
tomcat设置catalina.sh
export JAVA_OPTS="-server –Xms1024m -Xmx1024m -XX:+UseParallelOldGC -verbose:gc -Xloggc:…/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps"
-XX:+PrintCommandLineFlagsjvm参数可查看默认设置收集器类型
-XX:+PrintGCDetails亦可通过打印的GC日志的新生代、老年代名称判断

JVM监控

1.本机环境下,推荐一款idea上的插件VisualVM Launcher,实际就是联动了JDK开发包中自带的jvisualvm.exe监控软件。也可以设置远程监控。具体使用方法,可以参考这篇文章https://blog.csdn.net/wngpenghao/article/details/82884874IDEA Java性能分析插件VisualVM Launcher 配置(JAVA VisualVM 与Jconsole配置相同)
2.Linux的相关命令:
jstat命令可以对jvm从各维度进行统计,详细使用参考jstat命令查看jvm的GC情况
3.VM参数设置时,指定打印出gc日志
-Xloggc:…/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
详细的参数设置以及gc日志该如何阅读,可以参考java之GC日志该怎么看

JVM异常排查

1.保存dump
当使用监控软件或者命令查看发现JVM异常时,应第一时间保存下dump现场。
命令是jmap -dump:format=b,file=文件名[pid]
pid是服务进程
如果是使用jvisualvm就更方便了,直接点击如图所示的按钮即可:
在这里插入图片描述
2.分析dump
eclipse有一款插件叫做Memory Analyzer(MAT),但是目前idea并没有这款插件
此外,jhat是sun 1.6及以上版本中自带的一个用于分析JVM 堆DUMP 文件的工具,基于此工具可分析JVM HEAP 中对象的内存占用情况
jhat -J-Xmx1024M [file]
执行后等待console 中输入start HTTP server on port 7000 即可使用浏览器访问 IP:7000
可以特别关心下图标出的这个选项
在这里插入图片描述
这对于排查堆内存溢出非常有效

实战例子

由于实际工作中,能接触到JVM机会的机会并不多,所以笔者整理了一些经典实例
Metaspace溢出排查过程
分享一次 Java 内存泄漏的排查
一次生产的 JVM 优化案例
JVM成长之路,记录一次内存溢出导致频繁FGC的问题排查及解决
非常详细的jvm调优实例,性能瓶颈定位

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值