JVM虚拟机调优指导(一)——JVM虚拟机内存管理模型与运行参数

1 JVM虚拟机内存管理

1.1 JVM整体架构

在这里插入图片描述
JVM虚拟机中主要是由三部分构成,分别是类加载子系统、运行时数据区、执行引擎。
类加载子系统:Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
运行时数据区:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
执行引擎:执行引擎用于执行JVM字节码指令,主要有两种方式,分别是解释执行和编译执行,区别在于,解释执行是在执行时翻译成虚拟机指令执行,而编译执行是在执行之前先进行编译再执行。
垃圾回收器就是自动管理运行数据区的内存,将无用的内存占用进行清除,释放内存资源。
本地方法库、本地库接口:在jdk的底层中,有一些实现是需要调用本地方法完成的(使用c或c++写的方法),就是通过本地库接口调用完成的。比如:System.currentTimeMillis()方法。

1.2 运行时数据区

根据《Java虚拟机规范》中的规定,在运行时数据区将内存分为方法区(Method Area)、Java堆区(Java Heap)、Java虚拟机栈(Java Virtual Machine Stack)、程序计数器(Program Counter Register)、本地方法栈(Native Method Stacks)。

1.2.1 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。属于“线程私有”的内存区域。

1.2.1.1 编写代码
public class Test1 {

    public int add(){
        int a = 1;
        int b = 2;
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        int result = test1.add();
        System.out.println(result);
    }

}
1.2.1.2 查询class的汇编代码

javac Test1.java
javap -c Test1.class > t.txt

Compiled from "Test1.java"
public class cn.learn.jvm.Test1 {
  public cn.learn.jvm.Test1();
    Code:
       0: aload_0
       1: invokespecial #1  // Method java/lang/Object."<init>":()V
       4: return

  public int add();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: iload_3
       9: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class cn/learn/jvm/Test1
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method add:()I
      12: istore_2
      13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: iload_2
      17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      20: return
}

code所对应的编号就可以理解为计数器中所记录的执行编号。

1.2.2 Java虚拟机栈

Java虚拟机栈也是线程私有的,它的生命周期与线程相同。
Java虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
在这里插入图片描述

1.2.2.1 局部变量表
  • 局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量
  • 在Class文件中,方法的Code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。
  • 该表以变量槽(Variable Slot)为最小单位,一个slot可以存放32位以内的数据,比如:boolean、byte、char、short、int、float等数据,如果存储long、double类型数据,需要占用2个solt。
  • 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的变量槽数量。
  • 如果访问的是32位数据类型的变量,索引N就代表了使用第N个变量槽,如果访问的是64位数据类型的变量,则说明会同时使用第N和N+1两个变量槽。
  • 局部变量表中第0位索引的变量槽默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。其余参数则按照参数表顺序排列,占用从1开始的局部变量槽,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的变量槽。
1.2.2.2 操作数栈
  • 操作数栈也常被称为操作栈,它是一个先进后出栈。
  • 操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks数据项之中。
  • 操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
  • 方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。
  • 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,例如iadd指令,不能出现一个long和一个float使用iadd命令相加的情况。
1.2.2.3 动态连接
  • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
  • Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。
1.2.2.4 方法出口
  • 当一个方法开始执行后,只有两种方式退出这个方法。
  • 第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为“正常调用完成”。
  • 另外一种退出方式是在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为“异常调用完成”。这种方法的返回是不会给它的上层调用者提供任何返回值的。
  • 无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。
  • 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
1.2.2.5 实例

int a = 1; 入栈再到局部变量表的操作:
在这里插入图片描述
在这里插入图片描述

1.2.3 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

1.2.4 Java堆区

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。

1.2.4.1 jdk1.7堆内存模型

在这里插入图片描述

  • Young 年轻区(代)
    Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候, GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移
    动到Tenured区间。
  • Tenured 年老区
    Tenured区主要保存生命周期长的对象
  • Perm 永久区
    Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类。
  • Virtual区:最大内存和初始内存的差值,不是实际的区域
1.2.4.2 jdk1.8堆内存模型

在这里插入图片描述
年轻代:Eden + 2*Survivor
年老代:OldGen
用Metaspace(元数据空间)替换了Perm区
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。
在这里插入图片描述
上图所显示出的是默认状态下的空间分配情况,如果在没有指定堆内存大小时,默认初始堆内存为物理机内存的1/64,最大堆内存为物理机内存的1/4 或 1G。(JDK8的情况下)

1.2.4.3 为什么要放弃1.7中的永久区

This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。

1.2.5 方法区
  • 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息常量静态变量、即时编译器编译后的代码缓存等数据。
  • 《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。
1.2.6 对象的访问
  • Java程序会通过栈上的reference数据来操作堆上的具体对象。
  • 主流的访问方式主要有使用句柄和直接指针两种:
    使用句柄访问:Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息
    使用直接指针访问:Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销
  • 使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
  • 使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销。
  • HotSpot虚拟机采用的是指针访问方式实现。

2 虚拟机性能相关工具

2.1 JVM的运行参数

2.1.1三种参数类型
  • 标准参数
    -help
    -version
  • -X参数 (非标准参数)
    -Xint
    -Xcomp
  • -XX参数(使用率较高)
    -XX:newSize
    -XX:+UseSerialGC
2.1.2 标准参数

jvm的标准参数,一般都是很稳定的,在未来的JVM版本中不会改变,可以使用java -help检索出所有的标准参数。
通过-D设置系统属性参数
例如:java -Dstr=123 TestJVM

String str = System.getProperty(“str”);

2.1.3 -server与-client参数

可以通过-server或-client设置jvm的运行参数。

  • 它们的区别是Server VM的初始堆空间会大一些,默认使用的是并行垃圾回收器,启动慢运行快。
  • Client VM初始堆空间会小一些,使用串行的垃圾回收器
  • JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。
  • 32位操作系统
    如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。
    如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server模式,否则使用client模式。
  • 64位操作系统
    只有server类型,不支持client类型。

2.2 -X参数

jvm的-X参数是非标准参数,在不同版本的jvm中,参数可能会有所不同,可以通过java -X查看非标准参数。
java -X
-Xmixed 混合模式执行 (默认)
-Xint 仅解释模式执行
-Xbootclasspath:<用 ; 分隔的目录和 zip/jar 文件>
设置搜索路径以引导类和资源
-Xbootclasspath/a:<用 ; 分隔的目录和 zip/jar 文件>
附加在引导类路径末尾
-Xbootclasspath/p:<用 ; 分隔的目录和 zip/jar 文件>
置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc: 将 GC 状态记录在文件中 (带时间戳)
-Xbatch 禁用后台编译
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
-Xprof 输出 cpu 配置文件数据
-Xfuture 启用最严格的检查, 预期将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用 (请参阅文档)
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据 (默认)
-Xshare:on 要求使用共享类数据, 否则将失败。
-XshowSettings 显示所有设置并继续
-XshowSettings:all
显示所有设置并继续
-XshowSettings:vm 显示所有与 vm 相关的设置并继续
-XshowSettings:properties
显示所有属性设置并继续
-XshowSettings:locale
显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。

2.2.1 -Xint、-Xcomp、-Xmixed
  • 在解释模式(interpreted mode)下,-Xint标记会强制JVM执行所有的字节码,当然这会降低运行速度,通常低10倍或更多。
  • 然而,很多应用在使用-Xcomp也会有一些性能损失,当然这比使用-Xint损失的少,原因是-xcomp没有让JVM启用JIT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所有代码都进行编译的话,对于一些只执行一次的代码就没有意义了。
  • -Xmixed是混合模式,将解释模式与编译模式进行混合使用,由jvm自己决定,这是jvm默认的模式,也是推荐使用的模式。
  • 示例:强制设置运行模式
    #强制设置为解释模式
    [root@node01 test]# java -showversion -Xint TestJVM
    #强制设置为编译模式
    [root@node01 test]# java -showversion -Xcomp TestJVM
    #默认的混合模式
    [root@node01 test]# java -showversion TestJVM

2.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

2.4 -Xms与-Xmx参数

-Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。

2.5 查看正在运行的JVM参数

如果想要查看正在运行的jvm就需要借助于jinfo命令查看。
查找到这个应用的进程编号:jps -l
查看运行参数:
jinfo -flags 12076(进程编号)
Attaching to process ID 16232, please wait…
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=130023424 -XX:MaxHeapSize=2055208960 -XX:MaxNewSize=684720128 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=42991616 -XX:OldSize=87031808 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line:
#查看某一参数的值,用法:jinfo -flag <参数名> <进程id>
jinfo -flag MaxHeapSize 16232
-XX:MaxHeapSize=2055208960

2.6 jstat

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]

2.6.1 查看class加载统计(每隔2秒查询一次,查询3次)

jstat -class 16232 2000 3
Loaded Bytes Unloaded Bytes Time
5974 10837.3 1 0.9 3.06
5974 10837.3 1 0.9 3.06
5974 10837.3 1 0.9 3.06
说明:

  • Loaded:加载class的数量
  • Bytes:所占用空间大小
  • Unloaded:未加载数量
  • Bytes:未加载占用空间
  • Time:时间
2.6.2 查看编译统计

jstat -compiler 16232
Compiled Failed Invalid Time FailedType FailedMethod
3098 0 0 4.73 0
说明:

  • Compiled:编译数量。
  • Failed:失败数量
  • Invalid:不可用数量
  • Time:时间
  • FailedType:失败类型
  • FailedMethod:失败的方法
2.6.3 垃圾回收统计

jstat -gc 16232
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
6144.0 6656.0 0.0 4000.0 125952.0 77873.5 50688.0 5563.8 28928.0 27336.8 3840.0 3542.6 7 0.029 1 0.025 0.054

说明:
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:垃圾回收消耗总时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值