本篇是对《深入理解Java虚拟机----JVM高级特性与最佳实践》周志明作的第二版对应内容的一个读书笔记。
一、JVM运行时数据区
名字 | 线程共享/私有 | 存的什么 | 可能会报什么异常 |
程序计数器 | 线程私有 | 如果正在执行java方法,存的字节码指令的地址 如果正在执行native方法,程序计数器为空。 | 虚拟机规范中未规定 |
Java虚拟机栈 | 线程私有 | 每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。 方法栈帧放入虚拟机栈中。 | 1.线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常 2.如果扩展时无法申请到足够内存,抛出OutOfMemoryError异常 |
本地方法栈 | 线程私有 | 跟Java虚拟机栈类似,区别在于Java虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机使用Native方法服务。 | 同上 |
堆 | 线程共享 | 存放对象实例。是垃圾收集器管理的主要区域。 | 如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常 |
方法区 | 线程共享 | 存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 | 如果方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常 |
运行时常量池 | 线程共享 | 方法区的一部分。运行期间也可能将新的常量放入常量池中,这种特性的使用常见于String类的intern()方法 | 同上 |
二、各个区域出现内存溢出的定位、解决思路
2.1.程序计数器
该区域一般不会出现内存溢出,它占用的内存很小很小的,虚拟机规范中也并未指定它会跑什么异常。
2.2.栈溢出
HotSpot虚拟机并不区分java虚拟机栈和本地方法栈。
上面说了
每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
方法栈帧放入虚拟机栈中。
线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
如果扩展时无法申请到足够内存,抛出OutOfMemoryError异常
有1个参数设置栈空间大小
-Xss
栈溢出时日志报错的样子:
statck length:2402
Exception in thread "main" java.lang.StackOverflowError
at org.fenixsoft.oom.VMStackSOF.leak(xxxxxx.java:xx)
另一种
Exception in thead "main" java.lang.OutOfMemoryError: unable to create new native thread
栈溢出定位、解决思路:
1.在单个线程下:无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候虚拟机抛出的都是StackOverflowError异常。这样是可以把栈内存设置大的。
2.在多线程下:为每个线程的栈分配的内存越大,反而越容易产生栈内存溢出,抛出OutOfMemoryError异常。
原因:在操作系统分配给每个进程的内存是有限的情况下。Java进程的内存=堆+栈+方法区+程序计数器。
换言之,在堆、方法区、程序计数器内存固定的情况下,剩下的内存都被栈瓜分了。
而每个线程分到的栈内存越大、可以创建的线程数量就越小,在创建新线程时就越容易栈溢出。
所以:如果是因为建立过多的线程导致栈内存溢出,在不减少线程数的情况下,只能通过减少最大堆、减少栈容量来换取更多的线程。
2.3.堆溢出
上面说了,堆是用来放对象的。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
有3个参数跟设置堆空间和定位溢出有关
1.-Xms参数,设置堆的最小值
2.-Xmx参数,设置堆的最大值
如果最小值跟最大值一样,则避免了堆自动扩展
3.-XX:+HeapDumpOnOutOfMemoryError 设置虚拟机在出现内存溢出异常时Dump出当天内存堆转储快照,一遍事后分析。
堆溢出时日志报错的样子:
java.lang.OutOfMemoryError:Java heap space
Dumping heap to java_pidxxxx.hprof ...
Heap dump file created [xxxxx bytes in xxxx secs]
堆溢出定位、解决思路:
1.判断是内存溢出(Memory Overflow)还是内存泄漏(Memory Leak):先通过内存映像(jmap能生成堆内存映像)分析工具(比如jhat、VisualVM、Eclipse Memory Analyzer、IBM HeapAnalyzer)对Dump出来的堆转储快照进行分析,确认内存中的对象是否是必要的。
比如:MAT内存泄露分析(一)
2.如果是内存泄漏,通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的路径与GC Roots关联并导致垃圾回收器无法自动回收他们。定位出泄漏代码的位置,修改代码。
3.如果不是内存泄漏。看能不能把堆空间调大。检查代码,是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
2.4.方法区溢出
上面说了,方法区存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。如果方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
方法区空间大小的设置参数取决去分代,不同版本的JDK分代不一样,等会写1.8的分代。
在HotSpot虚拟机中的永久代就是管理方法区的。
有2个参数设置方法区空间:
1.-XX:PermSize
2.-XX:MaxPermSize
方法区溢出时日志报错的样子:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
...
三、虚拟机性能监控与故障处理工具
命令行工具:
3.1 jps 虚拟机进程状况工具
作用:
列出了正在运行的虚拟机进程,有进程ID,和执行主类名称(main()函数在的类)
命令格式:
jsp [options] [hostid]
options参数选项:
参数选项 | 作用 |
-q | 只输出进程ID,省略主类名称 |
-m | 输出启动时传递给主类main()方法的参数 |
-l | 输出主类全名,如果进程执行的是Jar包,则输出Jar包全路径 |
-v | 输出虚拟机进程启动时JVM参数 |
hostid: 可以通过RMI协议查询开启了RMI服务的远程虚拟机进程状态 ,hostid为RMI注册表中注册的主机名。
3.2 jstat 虚拟机统计信息监视工具
作用:
显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
命令格式:
jstat [ option vmid [interval[s|ms] [count]] ]
interval表示查询间隔
count表查询次数
vmid是进程ID,如果是远程虚拟机进程vmid的格式是[protocol:][//]lvmid[@hostname[:port]/servername]
option参数选项:
选项 | 作用 |
-class | 监视类装载、卸载数量、总空间以及类装载所消耗的时间 |
-gc | 监视Java堆状况,包括Eden区、两个surivor区、老年代、永久代等的容量、已用空间,GC时间合计等信息 |
-gccapacity | 监视内容与-gc基本相同,但输出主要关注Java堆各个区域使用到的最大、最小空间 |
-gcutil | 监视内容与-gc基本相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因 |
-gcnew | 监视新生代GC状况 |
-gcnewcapacity | 监视内容与-gcnew基本相同,输出主要关注使用到的最大、最小空间 |
-gcold | 监视老年代GC状况 |
-gcoldcapacity | 监视内存与-gcold基本相同,输出主要关注使用到的最大、最小空间 |
-gcpermcapacity | 输出永久代使用到的最大、最小空间 |
-compiler | 输出JIT编译器编译过的方法、耗时等信息 |
-printcompilation | 输出已被JIT编译的方法 |
3.3 jinfo java配置信息工具
作用:
实时查看和调整虚拟机各项参数
命令格式:
jinfo [option] pid
option参数选项:
选项 | 作用 |
-flag | 查看虚拟机参数 |
-flag name = value | 修改虚拟机参数 |
-sysprops | 把虚拟机进程的System.getProperties()的内容打印出来 |
3.4 jmap Java内存映像工具
作用:
生成堆内存转储快照。查询finalize执行队列、Java堆和永久代的详细信息。
除了jmap以外拿到堆内存映像转储快照的方式还有:
1.-XX:+HeapDumpOnOutOfMemoryError 设置该参数,让虚拟机在OOM异常出现后自动dump文件
2.-XX:+HeapDumpOnCtrlBreak 设置该参数,使用Ctrl+Break键让虚拟机生成dump文件
命令格式:
jmap [ option ] vmid
option参数选项:
选项 | 作用 |
-dump | 生成Java堆转储快照。格式为: -dump:[live, ]format=b,file=<filename>,其中live子参数说明是否只dump出存活的对象 |
-finalizerinfo | 显示在F-Queue中等待Finalizer线程执行finalize方法的对象。只在Linux/Solaris平台下有效 |
-heap | 显示Java堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在Linux/Solaris平台下有效 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
-permstata | 以ClassLoader为统计口径显示永久代内存状态。只在Linux/Solaris平台下有效 |
-F | 当虚拟机进程对-dump选项没有响应时,可使用这个选项强制生成dump快照。只在Linux/Solaris平台下有效 |
3.5 jhat 虚拟机堆转储快照分析工具
作用:
分析jmap生成的堆转储快照。
jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后可以在浏览器查看,一般是http://localhost:7000
不推荐使用jhat来分析堆转储快照,因为它太简陋了,一般推荐使用VisualVM或者Eclipse Memory Analyzer。
3.6 jstack Java堆栈跟踪工具
作用:
生成虚拟机当前时刻的线程快照,一般称为threaddump或者javacore文件。
线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定线程出现长时间卡顿的原因。
在另一篇博客中详细讲了这个jstack如果分析线程死锁,及生成的文件内容详情
【二十二】Java多线程之线程转储和分析(jstack)
命令格式:
jstack [option] vmid
option参数选项:
选项 | 作用 |
-F | 当正常数据的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用到本地方法的话,可以显示C/C++的堆栈 |
可视化工具
3.7 Jconsole
作用:这是一种基于JMX的可视化监视、管理工具。它的管理部分的功能是针对JMX MBean进行管理。
包括了内存(堆和非堆、执行GC)、线程(每个线程、检测死锁)、类、CPU使用情况、虚拟机概要(JVM参数)、MBean、监控。
可监控远程虚拟机的。
打开方式:windows在JAVA_HOME\bin下的Jconsole.exe
列表中会有本地所有的java进程,选择一个,进入监控界面
3.8 VisualVM
作用:覆盖了Jconsole的所有功能,并且还能扩展,并且对应用程序的实际性能影响很小可以直接应用在生产环境中。
基于NetBeans平台开发,具备了插件扩展功能,通过插件扩展支持可以做到:
1.显示虚拟机进程以及进程的配置、环境信息(类似于jps、jinfo的功能)
2.监视应用程序的CPU、GC、堆、方法区以及线程信息(类似于jstat、jstack的功能)
3.dump以及分析堆转储快照(类似于 jmap、jhat的功能)
4.方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
5.离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建议一个快照,可以将快照发送开发者处进行BUG反馈
可监控远程虚拟机的。
修改插件中心地址:
http://Visualvm java.net/pluginscenters.html 已关闭
https://visualvm.github.io/plugins.html 目前迁移到这里了
点击工具----插件,在弹出界面中点击设置,把URL换成https://visualvm.github.io/archive/uc/8u40/updates.xml.gz
详细方法见这篇博客:jvisualvm安装visualgc插件(java.net网站已关闭)
打开方式:windows在JAVA_HOME\bin下的JvisualVM.exe
推荐安装的插件:
1.生成、浏览堆转储快照。在“监视”页签中点击堆Dump,会得到一个以[heapdump]开头的堆转储快照,在里面可以执行OQL语句进行堆转储快照分析。
2.分析程序性能。在Profiler页签中,可以做程序运行期间方法级的CPU执行时间分析以及内存分析。因为对程序运行性能有较大的影响,不建议在生产环境中使用这项功能。
3.BTrace动态日志跟踪。在不停止目标程序的前提下,通过HotSpot虚拟机的HotSwap技术,动态加入原本并不存在的测试代码。
前面两个本来就自带,只需要自己安装一下BTrace插件就好了。