Java 虚拟机知识 - JVM 入门
1, 架构师面对 JVM 调优, 能做什么?
JVM 调优目的: 让系统运行更快, 更稳定.
背景问题: 1, 高并发; 2, 高吞吐;
2, 再次认识 Java
Java 技术体系
JDK: Java 程序设计语言; Java 虚拟机; Java 类库. 是支持 Java 程序开发的最小环境.
JRE: JavaSE API(类库)和 Java 虚拟机统称为 JRE, 是支持 Java 程序运行的标准环境.
Java 发展历史
1995 正是发布 Java, Write Once, Run Anywhere 的特点;
1996JDK1.0
1999HotSpot 虚拟机诞生;
2004JDK5
2014JDK8: 支持 Lambda, 移除了 HotSpot 永久代
2017 起, JDK 每年 3 月和 9 月发布一个大版本, 每六个大版本画出一个长期支持版 LTS, 三年的支持和更新
2018JDK8 - LTS 版本
JVM 虚拟机种类
HotSpot: OracleJDK 和 OpenJDK 中默认的 Java 虚拟机, 也是使用最广泛的 Java 虚拟机, 热点代码探测技术. Oracle 收购 Sun 之后, 将 JRockit 的优秀特性融合到 HotSpot 之中.
JRockit: 来自 BEA, 定位于专门为服务器硬件和服务端应用场景, 做了专门优化, 不关注程序启动速度, 因此 JRockit 内部不包含解释器实现, 全部代码都靠即时编译器编译后执行.号称世界上速度最快的 Java 虚拟机.
IBM J9: IBM 开源, 捐献给了 Eclipse 基金会管理.
3,JVM 虚拟机内存管理
为什么要了解 JVM 的内存管理?
对于 Java 程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个 new 操作去写配对的 delete/free 代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好。
不过,也正是因为 Java 程序员把控制内存的权力交给了 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。
3.1 整体架构
JVM 主要组成: 类加载系统, 运行时数据区, 执行引擎, 本地方法库与本地库接口
3.1.1 类加载系统
作用: 加载 class 文件, 形成可以被虚拟机直接使用的 Java 类型
1, 将 Class 文件加载到内存;
2, 数据校验, 转换解析和初始化;
3.1.2 运行时数据区
作用: JVM 管理的内存, 划分为若干不同的数据区域, 用于存储 Java 程序的数据.
3.1.3 执行引擎
作用:
1, 用于执行 JVM 字节码指令(解释执行和编译执行)
2, 垃圾回收器自动管理运行数据区的内存, 将无用的内存占用进行清除, 释放内存资源.
3.1.4 本地方法库与本地库接口
作用: JDK 底层用于调用系统本地方法的方法或者接口
3.2 运行时数据区
运行时数据区是 jvm 中最为重要的部分,也是我们在调优时需要重点关注的区域
3.2.1 程序计数器
作用: 当前线程执行字节码指令的指示器. 指向下一条需要执行的字节码指令. 分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
由于 Java 虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存.
代码展示:
反编译该 Java 代码的字节码文件:
命令行中, 打开字节码 class 文件所在位置, 执行: javap -c Demo1_ProgramCounter.class > ProgramCounter.txt
可以看到将 class 文件中字节码进行反汇编,得到上面的代码,其中 code 所对应的编号就可以理解为计数器中所记录的执行编号
3.2.2 Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。Java 虚拟机栈描述的是**Java方法
执行的线程内存模型:每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧**,用于存储局部变量表
、操作数栈
、动态连接
、方法出口
等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表
操作数栈
动态连接
方法出口
3.2.3 本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
3.2.4 Java 虚拟机堆
Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的**唯一目的就是存放对象实例
**,Java 世界里“几乎”所有的对象实例都在这里分配内存。需要注意的是,《Java 虚拟机规范》并没有对堆进行细致的划分,所以对于堆的讲解要基于具体的虚拟机,我们以使用最多的 HotSpot 虚拟机为例进行讲解。
Java 堆是垃圾收集器管理的内存区域,因此它也被称作“GC 堆”,这就是我们做 JVM 调优的重点区域部分。
Young 年轻区(代)
Young 区被划分为三部分,Eden 区和两个大小严格相同的 Survivor 区,其中,Survivor 区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在 Eden 区间变满的时候, GC 就会将存活的对象移到空闲的 Survivor 区间中,根据 JVM 的策略,在经过几次垃圾收集后,任然存活于 Survivor 的对象将被移动到 Tenured 区间。
Tenured 年老区
Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区,一般如果系统中用了 application 级别的缓存,缓存中的对象往往会被转移到这一区间。
Perm 永久区(注意 JDK8 中永久代使用元数据空间进行了替换, 也就是永久区使用了机器内存而不是 jvm 内存)
Perm 代主要保存 class,method,filed 对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的 class 没有被卸载掉,这样就造成了大量的 class 对象保存在了 perm 中,这种情况下,一般重新启动应用服务器可以解决问题。
Virtual 区
最大内存和初始内存的差值,就是 Virtual 区。
3.2.5 方法区
方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
《Java 虚拟机规范》中把方法区描述为堆的一个逻辑部分,它却有一个别名叫作“非堆”(Non-Heap),目的是与 Java 堆区分开来。
JDK8 之前将 HotSpot 虚拟机把收集器的分代设计扩展至方法区,所以可以将永久代看做是方法区,JDK8 之后废弃永久代,用元空间来代替。
4, 虚拟机性能相关工具
4.1 JVM 的运行参数
标准参数
jvm 的标准参数,一般都是很稳定的,在未来的 JVM 版本中不会改变,可以使用 java -help 检索出所有的标准参数。
-X 参数(非标准参数)
jvm 的-X 参数是非标准参数,在不同版本的 jvm 中,参数可能会有所不同,可以通过 java -X 查看非标准参数。
-XX 参数(使用率较高)
4.2 参数: -X
jvm 的-X 参数是非标准参数,在不同版本的 jvm 中,参数可能会有所不同,可以通过 java -X 查看非标准参数。
4.3 参数: -XX
-XX 参数也是非标准参数,主要用于 jvm 的调优和 debug 操作。
-XX 参数的使用有 2 种方式,一种是 boolean 类型,一种是非 boolean 类型
使用方法:
boolean 类型使用: 格式:-XX:[+-]<name> 表示启用或禁用<name>属性
如:-XX:+DisableExplicitGC 表示禁用手动调用 gc 操作,也就是说调用 System.gc()无效
非 boolean 类型使用: 格式:-XX:<name>=<value> 表示<name>属性的值为<value>
如:-XX:NewRatio=4 表示新生代和老年代的比值为 1:4
4.4 参数: -Xms 与-Xmx
**-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
**
**-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
**
**-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。
**
**适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快
**
4.5 查看正在运行的 jvm 参数
1, 启动一个 java 程序;
在 IDE 或者通过命令行启动一个带时延的程序,
启动该程序...
2, 查找该应用进程编号;
作用: 找到进程编号, 供第 3 步查看参数使用
操作: 命令行中执行jps -l
命令
3, 查看运行参数
4.6 jstat
jstat 命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
继续启动上面程序
1.查看 class 加载统计
2.查看编译统计
3.垃圾回收统计
设置每 500 毫秒打印 1 次, 一共打印 5 次
字段解释如下
S0C:第一个 Survivor 区的大小(KB)
S1C:第二个 Survivor 区的大小(KB)
S0U:第一个 Survivor 区的使用大小(KB)
S1U:第二个 Survivor 区的使用大小(KB)
EC:Eden 区的大小(KB)
EU:Eden 区的使用大小(KB)
OC:Old 区大小(KB)
OU:Old 使用大小(KB)
MC:方法区大小(KB)
MU:方法区使用大小(KB)
CCSC:压缩类空间大小(KB)
CCSU:压缩类空间使用大小(KB)
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
版权声明: 本文为 InfoQ 作者【小马哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/36c35415562f5f47385f84311】。文章转载请联系作者。
评论