一、什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
二、Java代码执行流程
三、JVM的组成
类加载子系统
- 类加载子系统负责从文件系统加载Class文件,Class文件在文件开头有特定的文件标识。
- ClassLoader只负责class文件的加载,至于它是否可运行,则由执行引擎决定。
- 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
运行时数据区
虚拟机内存或者Jvm内存,在整个计算机内存中开辟一块内存存储Jvm需要用到的对象,变量等,运行时数据区又分很多小的区域,分别为:程序计数器、虚拟机栈、本地方法栈、堆、方法区。JVM调优主要就是优化Heap堆 和 Method Area 方法区。
程序计数器
- 它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域。
- 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期一致。
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址。
虚拟机栈
每个线程创建的同时会创建一个JVM栈,JVM栈中每个栈帧存放的为当前线程中局部基本类型的变量、部分的返回结果,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址;每一个方法从被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈运行原理:栈中的数据都是以栈帧的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧2也被压入栈,B方法又调用了C方法,于是产生栈帧3也被压入栈…栈帧n也被压入栈依次执行完毕后,先弹出后进的栈帧n…再弹出栈帧3,再弹出栈帧2,再弹出栈帧1。JAVA虚拟机栈的最小单位可以理解为一个个栈帧,一个方法对应一个栈帧,一个栈帧可以执行很多指令。
方法区
- 方法区和堆一样,是各个线程共享的内存区域。
- 方法区在JVM启动的时候被创建,并且它的实际物理内存空间中和JVM堆区一样都可以是不连续的。
- 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。
- 关闭JVM就会释放这个区域的内存。
堆
- 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
- 堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大的一块内存空间。
- 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
- 所有线程共享堆区,在这里还可以划分线程私有的缓冲区(TLAB)。
执行引擎
- 执行引擎是Java虚拟机核心的组成部分之一。
- 任务就是将字节码指令解释/编译为对应平台上的本地机器指令。
本地方法
- 本地方法接口就是Java调用非Java代码的接口,其作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序。
- 目前该方法使用的越来越少了,除非是与硬件有关的应用。
- Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈也是线程私有的。
- 具体做法是本地方法栈中登记Native方法,在执行引擎执行时加载本地方法库。
总结
四、JVM垃圾回收
什么是垃圾回收机制
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。垃圾回收能有效的利用可以使用的内存,对内存中已经没有任何引用的对象进行清除和内存回收。
了解堆内存
先看下JVM堆内存是如何划分的,如图:
- JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)或元空间(MetaSpace)。
- 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
- 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
- 非堆内存用途:永久代,也称为方法区,存储程序运行时需要的一些数据,比如类的元数据、方法、常量、属性等。
注:在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
参数 | 描述 |
---|---|
-Xms | 堆内存初始分配大小 (单位m、g ) |
-Xmx | 堆内存最大允许大小 |
-Xns | 年轻代内存初始大小 |
-Xmn | 年轻代内存最大允许大小 |
-Xss | 每个线程的堆栈内存大小 |
-XX:MetaspaceSize | 初始元空间大小 |
-XX:MaxMetaspaceSize | 最大元空间大小 |
-XX:SurvivorRatio=8 | 年轻代中Eden区与Survivor区的容量比例值,默认为8,即8:1:1 |
-XX:NewRatio=3 | 年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3 |
-XX:+PrintGC | 输出GC日志 |
-XX:+PrintGCDetails | 输出GC的详细日志 |
-XX:+PrintGCTimeStamps | 打印GC发生的时间戳 |
-Xloggc | 日志文件的输出路径 |
-XX:+HeapDumpOnOutOfMemoryError | 当JVM发生OOM时,自动生成DUMP文件 |
-XX:HeapDumpPath | 生成DUMP文件的路径 |
-XX:PreternureSizeThreshold | 直接晋升老年代的对象大小 |
-XX:MaxTenuringThreshold | 晋升老年代的对象年龄,默认值15 |
-XX:+PrintCommandLineFlags | 查看命令行相关参数 |
垃圾回收算法
注:红色是标记的非活动对象,绿色是活动对象。
- 标记-清除(Mark-Sweep)
GC分为两个阶段,标记和清除。首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存,而不得已再次触发GC。
- 复制(Copy)
将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点需要两倍的内存空间。
- 标记-整理(Mark-Compact)
也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,然后清理掉边界以外的内存。此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。
一般年轻代中执行GC后,会有少量的对象存活,就会选用复制算法,只要付出少量的存活对象复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外过多内存空间分配,就需要使用标记-清理或者标记-整理算法来进行回收。
垃圾收集器
串行收集器
- Serial垃圾收集器
最基本、历史最悠久的垃圾回收器,单线程。收集时,必须暂停应用的工作线程,直到收集结束。
参数 | 描述 |
---|---|
-XX:+UseSerialGC | 使用串行回收器进行回收 |
并行收集器
- ParNew垃圾收集器
多条垃圾收集线程并行工作,在多核CPU下效率更高,应用线程仍然处于等待状态。
- Parallel Scavenge垃圾收集器
吞吐量优先的垃圾收集器。
参数 | 描述 |
---|---|
-XX:+UseParNewGC | 新生代进行并行回收,老年代仍旧使用串行回收 |
-XX:+UseParallelGC | 新生代使用Parallel收集器,老年代使用串行收集器 |
-XX:MaxGCPauseMillis | 最大停顿时间(配合ParallelGC) |
-XX:GCTimeRatio | 控制垃圾回收时间占比(配合ParallelGC) |
-XX:UseAdaptiveSizePolicy | 根据实际运行情况动态调整一些细节参数(配合ParallelGC) |
-XX:+UseParallelOldGC | 新生代和老年代都使用并行收集器 |
CMS收集器(Concurrent Mark Sweep)
CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤,包括:
初始标记(Initial Mark)
并发标记(Concurrent Mark)
重新标记(Remark)
并发清除(Concurrent Sweep)
其中,初始标记、重新标记这两个步骤仍然需要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间短。由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。
参数 | 描述 |
---|---|
-XX:+UseConcMarkSweepGC | 新生代使用ParNew收集器,老年代使用CMS收集器 |
-XX:+ UseCMSCompactAtFullCollection | Full GC后,进行一次整理 |
-XX:ParallelCMSThreads | 并行GC时进行内存回收的线程数量 |
-XX:ConcGCThreads | 并发CMS过程运行时的线程数 |
G1收集器(Garbage First)
G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
G1收集器工作工程分为4个步骤,包括:
初始标记(Initial Mark)
并发标记(Concurrent Mark)
最终标记(Final Mark)
筛选回收(Live Data Counting and Evacuation)
初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。
参数 | 描述 |
---|---|
-XX:+UseG1GC | 使用 G1 垃圾收集器 |
-XX:MaxGCPauseMillis | 期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到) |
XX:MaxTenuringThreshold | 提升年老代的最大临界值(tenuring threshold),默认值为15 |
-XX:ParallelGCThreads | 垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同 |
-XX:ConcGCThreads | 并发垃圾收集器使用的线程数量,默认值随JVM运行的平台不同而不同 |
-XX:G1ReservePercent | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性,默认值是 10 |
-XX:G1HeapRegionSize | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小,默认值将根据 heap size 算出最优解. 最小值为1Mb, 最大值为 32Mb |
XX:InitiatingHeapOccupancyPercent | 启动并发GC周期时的堆内存占用百分比,默认值为 45 |
五、JVM常用监控工具
jinfo命令
jinfo全称Java Configuration Info,主要作用是查看JVM配置参数
用法:jinfo -flag <name> PID
直接输入jinfo PID会打印一些java系统属性
在最后面还会打印相关的jvm参数信息
单独查看某个jvm参数
jinfo -flag 相关JVM参数 PID
jps命令
查看当前运行的java进程
用法很简单 直接输入jps或jps -l
jmap命令
用法一:打印某个进程的jvm堆信息
jmap -heap PID
用法二:查看堆内存(histogram)中的对象数量,大小 一般查看数量排名靠前的对象
jmap -histo PID
用法三:导出堆内存映像
jmap -dump:format=b,file=heap.hprof PID
jstat命令
jstat用的最多的是用来统计gc相关信息
用法:jstat -gcutil PID 间隔秒数
S0: Survivor 0区的空间使用率
S1: Survivor 1区的空间使用率
E: Eden区的空间使用率
O: 老年代的空间使用率
M: 元数据的空间使用率
CCS: 类指针压缩空间使用率
YGC: 新生代GC次数
YGCT: 新生代GC总时长
FGC: Full GC次数
FGCT: Full GC总时长
GCT: 总共的GC时长
jstack命令
jstack主要的用途是打印出Thread dump,用来查看线程是否出现死锁
用法:jstack PID > stack.log
查找死锁可以在dump出的文件里直接搜索BLOCKED,deadlock等关键字
jvisualvm程序
jvisualvm能够监控内存泄露,跟踪垃圾回收,执行时内存、cpu分析,线程分析等,它是一款图形化界面工具,JDK中自带(1.6版本以上),直接运行即可。
查看线程信息
实时监控堆内存情况
监视程序运行时的各项指标
arthas工具
Arthas是Alibaba开源的Java诊断工具,功能非常强大。arthas工具下载地址:https://alibaba.github.io/arthas/arthas-boot.jar
用法:java -jar arthas-boot.jar PID
工具内部命令集合
dashboard命令展示