JVM完全指南
一:虚拟机内存图解
JAVA程序运行与虚拟机之上,运行时需要内存空间。虚拟机执行JAVA程序的过程中会把它管理的内存划分为不同的数据区域方便管理。
虚拟机管理内存数据区域划分如下图:
数据区域分类:
方法区:(Method Area)
虚拟机栈:(VM Stack)
本地方法栈 :(Native Method Stack)
堆:(Heap)
程序计数器 :(Program Counter Register)
直接内存:(Direct Memory)
说明:
1. 程序计数器
行号指示器,字节码指令的分支、循环、跳转、异常处理、线程恢复(CPU切换),每条线程都需要一个独立的计数器,线程私有内存互不影响,该区域不会发生内存溢出异常。
2. 虚拟机栈
是线程私有的,声明周期与线程相同,虚拟机栈是Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧,即方法运行期间的基础数据结构,栈帧用于存储:局部变量表、操作数栈、动态链接、方法出口等,每个方法执行中都对应虚拟机栈帧从入栈到处栈的过程。
是一种数据结构,是虚拟机中的局部变量表,对应物理层之上的程序数据模型。
局部变量表,是一种程序运行数据模型,存放了编译期可知的各种数据类型例如:
Boolean、byte、char、short、int、float、long、double、对象引用类型(对象内存地址变量,指针或句柄),程序运行时,根据局部变量表分配栈帧空间大小,在运行中,大小是不变的异常类型:stackOverFlowError 线程请求栈深度大于虚拟机允许深度 OutOfMemory 内存空间耗尽无法进行扩展。
3. 本地方法栈
与虚拟机栈类似,虚拟机栈为Java程序服务,本地方法栈支持虚拟机的运行服务,具体实现由虚拟机厂商决定,也会抛出 stackOverFlowError、OutOfMemory异常。
4. 堆
是虚拟机管理内存中最大的一部分,被所有线程共享,用于存放对象实例(对象、数组),物理上不连续的内存空间,由于GC收集器,分代收集,所以划分为:新生代 Eden、From SurVivor空间、To SurVivor空间,allot buffer(分配空间),可能会划分出多个线程私有的缓冲区,老年代。
5. 方法区
与堆一样属于线程共享的内存区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码(动态加载OSGI)等数据。理论上属于java虚拟机的一部分,为了区分开来叫做 Non-Heap非堆。
这个区域可以选择不进行垃圾回收,该区域回收目的主要是常量池的回收,及类型的卸载class,内存区不足时会抛出OutOfMemory异常
运行时常量池:
方法区的一部分,Class的版本、字段、接口、方法等,及编译期生成的各种字面量、符号引用,编译类加载后存放在该区域。会抛出OutOfMemory异常。
6. 直接内存
直接内存不属于虚拟内存区域,是一种基于通道与缓冲区的IO方式,可以使用本地函数直接分配堆外内存,在堆中存储引用的外部内存地址,通过引用完成对直接引用内存的操作,1.4之后提供的NIO显著提高效率,避免了堆内存与Native内存的来回复制操作,不受虚拟机内存控制,会抛出OUtOfMemory异常。
二:对象访问内部实现过程
对象访问 涉及到对象的地址变更状态变更,内存地址移动,变量、接口、实现类、方法、父类型等。
1、 句柄方式 (访问)
2、指针方式 (访问)
优缺点:
句柄访问方式:reference中存储的是稳定的地址,对象变更时只会改变句柄实例数据指针,引用本身不需要修改
指针访问方式:优点速度快,节省了指针定位时间开销
三:内存区域控制参数及对应溢出异常
开发过程中,或程序运行过程中每次遇到OutOfMemory异常或GC异常或StackOverflowError异常我们都是一堆参数乱配,都把值调大,只是大体知道是跟jvm内存分配有关,具体应该怎么调,对应的异常应该调整那些参数,或者换句话说,jvm内存分配区域中都分别对应那些参数大多数情况下都是不知道的,只是把相关的参数跳上去,预期结果都是应该起作用,到底能不能起作用,自己心里也没底。下面就来说一下jvm堆、栈、方法区等内存区域对应的参数,及每个区域可能抛出的异常类型,发生异常的场景分析。
1、参数类型
- 堆空间参数
- 栈空间参数
- 方法区空间参数
- 本机直接内存参数
2、异常类型
1.OutOfMemory异常
2.StackOverflowError异常
3、辅助参数说明
- -XX:+HeapDumpOnOutOfMemoryError 打印堆内存异常时打印出快照信息
- -XX:+HeapDumpPath 快照输出路径
- -Xmn指定eden区的大小
- -XX:SurvirorRation来调整幸存区的大小
- -XX:PretenureSizeThreshold设置进入老年代的阀值
4、参数说明、对应场景的异常
1.堆内存参数
-Xms:堆最小值(新生代和老年代之和)
-Xmx:堆最大值(新生代和老年代之和)
当最小值=最大值时,这时堆内存是不可扩展的。
例:-Xms80M -Xmx80M
通常将-Xmx和-Xms设置为一样的大小来减少gc的次数,堆内存不足时抛出OutOfMemoryError异常。
2.栈内存参数
-Xss
例:-Xss128k
单线程下无论栈帧太大还是栈容量太小,及引用深度超过虚拟机允许深度都会抛出StackOverflowError每个方法压入栈的帧大小是不一致的。多线程下当每个线程分配栈帧太大内存不能够扩展时抛出OutOfMemoryError异常线程栈帧越大,可创建的线程越少。
3.方法区参数
-XX:PermSize方法区内存最小值
-XX:MaxPermSize 方法区内存最大值
各个线程共享的内存区域,主要用来存储类的元数据、常量、静态变量、即时编译器编译后的代码等数据
例:-XX:PermSize=20M -XX:MaxPermSize=20M
异常类型 OutOfMemoryError :
原因:常量过多,或代理反射等使用频繁
4.本机直接内存参数
-XX:MaxDirectMemorySize
例:-XX:MaxDirectMemorySize=10M
不足时抛出OutOfMemory异常
四:垃圾收集算法
经典的垃圾回收算法以下几种
1、标记--清除算法(Mark-Sweep)
回收前状态:
回收后状态:
优缺点:
算法执行分为两个阶段标记与清除,所有的回收算法,基本都
基于标记回收算法做了深度优化
缺点:效率问题,内存空间碎片(不连续的空间)
2、复制算法(Copying)
回收前状态:
Eden内存空间 8
Survivor1空间(From空间)1
Survivor2空间(To空间) 1
Eden内存空间与Survivor空间 8:1
回收后状态:
Survivor1空间(From空间)1
Eden内存空间与Survivor空间 8:1
优缺点:
比较标记清除算法,避免了回收造成的内存碎片问题
缺点:以局部的内存空间牺牲为代价,不过空间的浪费比较小,默认8:1的比例1是浪费的。
复制也有一定的效率与空间成本
3、标记整理算法(Mark-Compact)
回收前状态:
回收后状态:
优缺点:
避免了,空间的浪费,与内存碎片问题。
缺点:整理时复制有效率成本。
五:垃圾收集器
1、七种垃圾收集器
(1) Serial(串行GC)-XX:+UseSerialGC
(2) ParNew(并行GC)-XX:+UseParNewGC
(3) Parallel Scavenge(并行回收GC)
(4) Serial Old(MSC)(串行GC)-XX:+UseSerialGC
(5) CMS(并发GC)-XX:+UseConcMarkSweepGC
(6) Parallel Old(并行GC)-XX:+UseParallelOldGC
(7) G1(JDK1.7update14才可以正式商用)
2、1~3用于年轻代垃圾回收:
年轻代的垃圾回收称为minor GC
3、4~6用于年老代垃圾回收(当然也可以用于方法区的回收):
年老代的垃圾回收称为full GC
G1独立完成"分代垃圾回收"
注意:并行与并发
并行:多条垃圾回收线程同时操作
并发:垃圾回收线程与用户线程一起操作
4、常用五种组合
Serial/Serial Old
ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
ParNew/CMS:当下比较高效的组合
Parallel Scavenge/Parallel Old:自动管理的组合
G1:最先进的收集器,但是需要JDK1.7update14以上
5、Serial/Serial Old
年轻代Serial收集器采用单个GC线程实现"复制"算法(包括扫描、复制)
年老代Serial Old收集器采用单个GC线程实现"标记-整理"算法
Serial与Serial Old都会暂停所有用户线程(即STW)
说明:
STW(stop the world):编译代码时为每一个方法注入safepoint(方法中循环结束的点、方法执行结束的点),在暂停应用时,需要等待所有的用户线程进入safepoint,之后暂停所有线程,然后进行垃圾回收。
适用场合:
CPU核数<2,物理内存<2G的机器(简单来讲,单CPU,新生代空间较小且对STW时间要求不高的情况下使用)
-XX:UseSerialGC:强制使用该GC组合
-XX:PrintGCApplicationStoppedTime:查看STW时间
6、ParNew/Serial Old:
ParNew除了采用多GC线程来实现复制算法以外,其他都与Serial一样,但是此组合中的Serial Old又是一个单GC线程,所以该组合是一个比较尴尬的组合,在单CPU情况下没有Serial/Serial Old速度快(因为ParNew多线程需要切换),在多CPU情况下又没有之后的三种组合快(因为Serial Old是单GC线程),所以使用其实不多。
-XX:ParallelGCThreads:指定ParNew GC线程的数量,默认与CPU核数相同,该参数在于CMS GC组合时,也可能会用到
7、Parallel Scavenge/Parallel Old:
特点:
年轻代Parallel Scavenge收集器采用多个GC线程实现"复制"算法(包括扫描、复制)年老代Parallel Old收集器采用多个GC线程实现"标记-整理"算ParallelScavenge与Parallel Old都会暂停所有用户线程(即STW)
说明:
吞吐量:CPU运行代码时间/(CPU运行代码时间+GC时间)CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,说明CPU利用率越高,所以主要用于处理很多的CPU计算任务而用户交互任务较少的情况)
参数设置:
-XX:+UseParallelOldGC:使用该GC组合
-XX:GCTimeRatio:直接设置吞吐量大小,假设设为19,则允许的最大GC时间占总时间的1/(1+19),默认值为99,即1/(1+99)
-XX:MaxGCPauseMillis:最大GC停顿时间,该参数并非越小越好
-XX:+UseAdaptiveSizePolicy:开启该参数,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数就不起作用了,虚拟机会自动收集监控信息,动态调整这些参数以提供最合适的的停顿时间或者最大的吞吐量(GC自适应调节策略),而我们需要设置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio两个参数就好(当然-Xms也指定上与-Xmx相同就好)
注意:
-XX:GCTimeRatio和-XX:MaxGCPauseMillis设置一个就好
不开启-XX:+UseAdaptiveSizePolicy,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数依旧可以配置,以resin服务器为例
<jvm-arg>-Xms2048m</jvm-arg> <jvm-arg>-Xmx2048m</jvm-arg> <jvm-arg>-Xmn512m</jvm-arg> <jvm-arg>-Xss1m</jvm-arg> <jvm-arg>-XX:PermSize=256M</jvm-arg> <jvm-arg>-XX:MaxPermSize=256M</jvm-arg> <jvm-arg>-XX:SurvivorRatio=8</jvm-arg> <jvm-arg>-XX:MaxTenuringThreshold=15</jvm-arg> <jvm-arg>-XX:+UseParallelOldGC</jvm-arg> <jvm-arg>-XX:GCTimeRatio=19</jvm-arg> <jvm-arg>-XX:+PrintGCDetails</jvm-arg> <jvm-arg>-XX:+PrintGCTimeStamps</jvm-arg>
适用场合:
很多的CPU计算任务而用户交互任务较少的情况不想自己去过多的关注GC参数,想让虚拟机自己进行调优工作
8、调优方法
8.1 新对象预留新生代
由于fullGC(老年代)的成本远比minorGC(新生代和老年代)的成本大,所以给应用分配一个合理的新生代空间,尽量将对象分配到新生代减小fullGC的频率
8.2 大对象进入老年代
将大对象直接分配到老年代,保持新生代对象的结构的完整性,以提高GC效率, 以通过-XX:PretenureSizeThreshold设置进入老年代的阀值
8.3 稳定与震荡的堆大小
稳定的对大小是对垃圾回收有利的,方法将-Xms和-Xmx的大小一致
8.4 吞吐量优先
尽可能减少系统执行垃圾回收的总时间,故采用并行垃圾回收器
-XX:+UseParallelGC或使用-XX:+UseParallelOldGC
8.5 降低停顿
使用CMS回收器,同时减少fullGC的次数
9、获取gc信息的方法
9.1 -verbose:gc或者-XX:+PrintGC 获取gc信息
9.2 -XX:+PrintGCDetails 获取更加详细的gc信息
9.3 -XX:+PrintGCTimeStamps 获取GC的频率和间隔
9.4 -XX:+PrintHeapAtGC 获取堆的使用情况
9.5 -Xloggc:D:\gc.log 指定日志情况的保存路径
10、jvm调优实战-tomcat启动加速
在tomcat的bin/catalina.bat文件的开头添加相关的配置
六:监控工具
监控工具:一般问题定位,性能调优都会使用到。
1、jps
Jps是参照Unix系统的取名规则命名的,而他的功能和ps的功能类似,可以列举正在运行的饿虚拟机进程并显示虚拟机执行的主类以及这些进程的唯一ID(LVMID,对应本机来说和PID相同),他的用法如下:
Jps [option] [hostid]
jps -q 只输出LVMID
jps -m 输出JVM启动时传给主类的方法
jps -l 输出主类的全名,如果是Jar则输出jar的路径
jps -v 输出JVM的启动参数
2、jstat
jstat主要用于监控虚拟机的各种运行状态信息,如类的装载、内存、垃圾回收、JIT编译器等,在没有GUI的服务器上,这款工具是首选的一款监控工具。其用法如下:
jstat [option vmid [interval [s|ms] [vount] ] ]
jstat 监控内容 线程好 刷新时间间隔 次数
jstat –gc 20445 1 20 :监视Java堆,包含eden、2个survivor区、old区和永久带区域的容量、已用空间、GC时间合计等信息
jstat –gcutil 20445 1 20:监视内容与-gc相同,但输出主要关注已使用空间占总空间的百分比
jstat –class 20445 1 20:监视类的装载、卸载数量以及类的装载总空间和耗费时间等
.......-gccapcity......:监视内容与-gc相同,但输出主要关注Java区域用到的最大和最小空间
.......-gccause........:与-gcutil输出信息相同,额外输出导致上次GC产生的原因
.......-gcnew..........:监控新生代的GC情况
.......-gcnewcapacity..:与-gcnew监控信息相同,输出主要关注使用到的最大和最小空间
.......-gcold..........:监控老生代的GC情况
.......-gcoldcapacity..:与-gcold监控信息相同,输出主要关注使用到的最大和最小空间
.......-gcpermcapacity.:输出永久带用到的最大和最小空间
.......-compiler.......:输出JIT编译器编译过的方法、耗时信息
.......-printcompilation:输出已经被JIT编译的方法
3、jinfo
jinfo的作用是实时查看虚拟机的各项参数信息jps –v可以查看虚拟机在启动时被显式指定的参数信息,但是如果你想知道默认的一些参数信息呢?除了去查询对应的资料以外,jinfo就显得很重要了。jinfo的用法如下:
Jinfo [option] pid
4、jmap
map用于生成堆快照(heapdump)。当然我们有很多方法可以取到对应的dump信息,如我们通过JVM启动时加入启动参数 –XX:HeapDumpOnOutOfMemoryError参数,可以让JVM在出现内存溢出错误的时候自动生成dump文件,亦可以通过-XX:HeapDumpOnCtrlBreak参数,在运行时使用ctrl+break按键生成dump文件,当然我们也可以使用kill -3 pid的方式去恐吓JVM生成dump文件。Jmap的作用不仅仅是为了获取dump文件,还可以用于查询finalize执行队列、Java堆和永久带的详细信息,如空间使用率、垃圾回收器等。其运行格式如下:
Jmap [option] vmip
监控堆栈信息主要用来定位问题的原因,生成堆栈快照
.......-dump......:生成对应的dump信息,用法为-dump:[live,]format=b,file={fileName}
.......-finalizerinfo......:显示在F-Queue中等待的Finalizer方法的对象(只在linux下生效)
.......-heap......:显示堆的详细信息、垃圾回收器信息、参数配置、分代详情等
.......-histo......:显示堆栈中的对象的统计信息,包含类、实例数量和合计容量
.......-permstat......:以ClassLoder为统计口径显示永久带的内存状态
.......-F......:虚拟机对-dump无响应时可使用这个选项强制生成dump快照
例子:jmap -dump:format=b,file=yhj.dump 20445
5、jstack
Jstack用于JVM当前时刻的线程快照,又称threaddump文件,它是JVM当前每一条线程正在执行的堆栈信息的集合。生成线程快照的主要目的是为了定位线程出现长时间停顿的原因,如线程死锁、死循环、请求外部时长过长导致线程停顿的原因。通过jstack我们就可以知道哪些进程在后台做些什么?在等待什么资源等!其运行格式如下:
Jstack [option] vmid
-F 当正常输出的请求不响应时强制输出线程堆栈
-l 除堆栈信息外,显示关于锁的附加信息
-m 显示native方法的堆栈信息
6、jconsole
在JDK的bin目录下,监控内存,thread,堆栈等
7、jprofile
类似于jconsole,比jconsole监控信息更全面,内存,线程,包,cup 类,堆栈,等等
陛下...看完奏折,点个赞再走吧!
文章转载自公众号
JAVA烂猪皮
- ☕【JVM技术指南】「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"【下部】
承接上文 (完结撒花1-52系列)[JVM技术指南]「JVM总结笔记」Java虚拟机垃圾回收认知和调优的"思南(司南)"[上部] 并行收集器 并行收集器(也称为吞吐量收集器)是类似 ...
- JVM 必备指南
简介 Java虚拟机(JVM)是Java应用的运行环境,从一般意义上来讲,JVM是通过规范来定义的一个虚拟的计算机,被设计用来解释执行从Java源码编译而来的字节码.更通俗地说,JVM是指对这个规范的 ...
- JVM必备指南(转)
本文由 ImportNew - xiafei 翻译自 anturis.欢迎加入翻译小组.转载请见文末要求. 简介 Java虚拟机(JVM)是Java应用的运行环境,从一般意义上来讲,JVM是通过规范来 ...
- 写给小白的JVM学习指南
Java 虚拟机是学习 Java 的基础,也是迈入高级 Java 开发工程师的必备知识点.所以今天这篇文章我们来聊聊如何从零开始学习 Java 虚拟机. 基础 对于刚刚接触 JVM 的同学来说,JVM ...
- JVM 入门指南
一.什么是JVM? JVM: JVM是Java Virtual Machine的缩写,中文翻译为Java虚拟机.JVM 可以看作一台抽象的计算机,如同真实的计算机一样,它有自己的指令集和各种运行时区域 ...
- JVM Troubleshooting
案例分享:如何通过JVM crash 的日志和core dump定位和分析Instrument引起的JVM crash https://docs.oracle.com/javase/7/docs/we ...
- JVM 理论基础目录(待更新,系列完全写完后会统一整理好)
参考文档: [1] Java 虚拟机规范(Java SE 8版) [2] 深入理解 Java 虚拟机: JVM 高级特性与最佳实践 周志明 本系列的更新快慢全部随意,介意者请海涵. 一 .JVM 入门 ...
- java程序员常用的八个工具
以下这8个工具,从代码构建到错误挤压,覆盖Java开发的全域.学习这些工具可以帮助你改善代码质量,成为一个更高效的Java开发人员. 1.Eclipse 尽管IntelliJ IDEA.NetBean ...
- 常用工具说明--Java的常用工具
1.Eclipse.IntelliJ IDEA Eclipse是IDE领域的瑞士军刀,有着大量定制的接口和无数的插件.它无处不在,后面本文将推荐的其他所有工具都提供Eclipse插件. Eclipse ...
随机推荐
- 《手把手教你学C语言》学习笔记(10)--- 程序的循环控制
C语言程序设计中,有些代码需要重复执行很多次,循环主要有三类: 一.for循环 1.基本格式为:for(表达式1:表达式2:表达式3){ //表达式1:循环变量赋初值 //表达式2:循环变量满足的条件 ...
- vim的使用技巧--模式入门
vim作为编辑器之神,一直都是程序爱好者的最爱,与一般的编辑器的最大不同就是对模式的把握更加的细腻和得当.普通编辑主要分为使用菜单和使用键盘,菜单就是输入命令作用,键盘主要用来输入文本,中间穿插着使用 ...
- Spring Boot学习——第一个Spring Boot程序
依照下面的步骤创建项目: 点击 Next 项目介绍: Application.java中的主要代码: @SpringBootApplication public class ReaderApplica ...
- mysql中PDO参数化引号引起的Warning: PDOStatement::execute(): SQLSTATE[HY093]:报错
sql语句之前是这样写的:INSERT INTO tablename SET a = ':a',b = ':b',c = ':c',d = :d,e = :e $data = array ( [':a ...
- C++ 找不到方法标识符
其实原因是这个CPP并没有面向对象的结构. 所以进行编译时是“顺序编译”的,而main函数的定义又在A的定义之前.自然找不到标识符了.
- ef core 2.1 利用Query Type查询视图
ef core新加入的功能“Query Type”可以让我们很方便的查询视图而不需要做任何特殊处理.不过在代码上和普通的查询有些不同. 先贴文档:https://docs.microsoft.com/ ...
- 你值得关注的几种常见的js设计模式
前言 潜水了一段时间,今天空闲时间复盘下之前的知识点,聊聊 js 几种常见的设计模式. 掌握 JavaScript 中常见的一些设计模式,对我们书写规范性代码,可维护性代码有很大的帮助. ps:最近在 ...
- Ruby on rails初体验(一)
接触ruby on rails 已经有一段时间了,想记录一下自己的rails历程.自己写一些小例子来帮助学习. Rails 适用于那些以数据为中心的应用,很多应用的核心部分包括一个数据库,这些引用的 ...
- 改变Linux的DNS解析顺序(DNS到hosts)
在Linux中,往往解析一个域名时,先会找/etc/hosts文件,如果/etc/hosts文件没有对应,才会去找DNS,那么有什么方式,让主机先找DNS呢? 当然有,在/etc/nsswitch.c ...
- DotnetBrowser高级教程-(4)使用MVC框架2-接收与返回模型
在上一节,我们搭建了基本的mvc框架,这一节,我们将实现数据的接受与返回,具体操作如下: 1.新建Model目录,新增模型类Person.cs,代码如下: public class Person { ...