# jvm-learn **Repository Path**: wh543/jvm-learn ## Basic Information - **Project Name**: jvm-learn - **Description**: jvm学习笔记 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-02-02 - **Last Updated**: 2021-03-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JVM 引言 ## 编程语言 ![](.README_images/88f035c7.png) - 面向过程、面向对象、面向函数 - 静态类型、动态类型 - 编译执行、解释执行 - 有虚拟机、无虚拟机 - 有GC、无GC Java是一种面向对象、静态类型、编译执行,有VM/GC和运行时、跨平台的高级语言 ## 定义 Java Virtual Machine - java程序的运行环境(java 二进制字节码的运行环境) ## 优点 - 一次编写,到处执行 - 自动内存管理,垃圾回收功能 - 数值下标越界检查 - 多态 ## JVM、JRE、JDK 之间的关系 ![](.README_images/00027c40.png) # 字节码与类加载 java语言规范以及JVM规范下载地址:https://docs.oracle.com/javase/specs/index.html ### 什么是字节码 ​ Java bytecode 由单字节(byte)的指令组成,理论上最多支持256个操作码(opcode)。实际上Java只使用了200左右的操作码,还有一些操作码则保留给调试操作。 ​ 以下是编译后的class文件使用16进制查看的结果(使用javap -verbose命令查看的则是这些字节码的助记符Mnemonic): ​ ![](.README_images/43e4b708.png) ​ 根据指令的性质,主要分为四个大类: 1. 栈操作指令,包括与局部变量交互的指令 2. 程序流程控制指令 3. 对象操作指令,包括方法调用指令 4. 算术运算以及类型转换指令 生成字节码 ```java package com.wenhao.bytecode; public class HelloByteCode { public static void main(String[] args) { HelloByteCode helloByteCode = new HelloByteCode(); } } ``` ![](.README_images/0eda3644.png) ``` Compiled from "HelloByteCode.java" public class com.wenhao.bytecode.HelloByteCode { public com.wenhao.bytecode.HelloByteCode(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class com/wenhao/bytecode/HelloByteCode 3: dup 4: invokespecial #3 // Method "":()V 7: astore_1 8: return } ``` 字节码中常用的load和store,指的是虚拟机栈加载本地变量中的数据,处理后存到本地变量的过程,其实就是入栈出栈的一个过程。 ![](.README_images/dcf619cb.png) 通过-verbose打印详尽的信息。 java -p -verbose HelloByteCode ```c Classfile /Users/liuwenhao/code/jvm-learn/target/classes/com/wenhao/bytecode/HelloByteCode.class Last modified 2021-2-2; size 495 bytes MD5 checksum dccb6a11373890b2fcbed372bac81d8c Compiled from "HelloByteCode.java" public class com.wenhao.bytecode.HelloByteCode minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#20 // java/lang/Object."":()V #2 = Class #21 // com/wenhao/bytecode/HelloByteCode #3 = Methodref #2.#20 // com/wenhao/bytecode/HelloByteCode."":()V #4 = Class #22 // java/lang/Object #5 = Utf8 #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 Lcom/wenhao/bytecode/HelloByteCode; #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = Utf8 args #15 = Utf8 [Ljava/lang/String; #16 = Utf8 helloByteCode #17 = Utf8 MethodParameters #18 = Utf8 SourceFile #19 = Utf8 HelloByteCode.java #20 = NameAndType #5:#6 // "":()V #21 = Utf8 com/wenhao/bytecode/HelloByteCode #22 = Utf8 java/lang/Object { public com.wenhao.bytecode.HelloByteCode(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/wenhao/bytecode/HelloByteCode; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: new #2 // class com/wenhao/bytecode/HelloByteCode 3: dup 4: invokespecial #3 // Method "":()V 7: astore_1 8: return LineNumberTable: line 6: 0 line 7: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; 8 1 1 helloByteCode Lcom/wenhao/bytecode/HelloByteCode; MethodParameters: Name Flags args } SourceFile: "HelloByteCode.java" ``` ### 字节码的运行时结构 JVM是一台基于栈的计算机器。每个线程都有一个独属于自己的线程(JVM stack),用于存储栈帧(Frame)。每次方法调用,JVM都会自动创建一个栈帧。栈帧由**操作数栈**(Operant Stack)、**局部变量**表(Local Variables),**数组**以及一个**指向运行时常量池的引用(A reference to the run-time constant pool)**、**方法返回地址**(Return Address)组成。 局部变量表:存放方法中定义的局部变量以及方法的参数。局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。 操作数栈:以压栈和出栈的方式存储操作数 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用时为了支持调用过程中的动态链接(Dynamic Linking) ​ 1.静态解析:类加载的时候,符号引用就转化成直接引用。 ​ 2.动态连接:运行期间转化为直接引用。 方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇到异常,并且这个异常没有在方法内得到处理。 四则运行的例子 ```java package com.wenhao.bytecode; /** * 移动平均数 */ public class MovingAverage { private int count = 0; private double sum = 0.0D; public void submit(double value) { this.count++; this.sum += value; } public double getAvg() { if (0 == this.count) { return sum; } return this.sum / this.count; } } ``` ```c++ Classfile /Users/liuwenhao/code/jvm-learn/target/classes/com/wenhao/bytecode/MovingAverage.class Last modified 2021-2-2; size 657 bytes MD5 checksum 7fb5be36412f5afb967e51c9691a13a8 Compiled from "MovingAverage.java" public class com.wenhao.bytecode.MovingAverage minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#26 // java/lang/Object."":()V #2 = Fieldref #4.#27 // com/wenhao/bytecode/MovingAverage.count:I #3 = Fieldref #4.#28 // com/wenhao/bytecode/MovingAverage.sum:D #4 = Class #29 // com/wenhao/bytecode/MovingAverage #5 = Class #30 // java/lang/Object #6 = Utf8 count #7 = Utf8 I #8 = Utf8 sum #9 = Utf8 D #10 = Utf8 #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 LocalVariableTable #15 = Utf8 this #16 = Utf8 Lcom/wenhao/bytecode/MovingAverage; #17 = Utf8 submit #18 = Utf8 (D)V #19 = Utf8 value #20 = Utf8 MethodParameters #21 = Utf8 getAvg #22 = Utf8 ()D #23 = Utf8 StackMapTable #24 = Utf8 SourceFile #25 = Utf8 MovingAverage.java #26 = NameAndType #10:#11 // "":()V #27 = NameAndType #6:#7 // count:I #28 = NameAndType #8:#9 // sum:D #29 = Utf8 com/wenhao/bytecode/MovingAverage #30 = Utf8 java/lang/Object { public com.wenhao.bytecode.MovingAverage(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: iconst_0 6: putfield #2 // Field count:I 9: aload_0 10: dconst_0 11: putfield #3 // Field sum:D 14: return LineNumberTable: line 6: 0 line 8: 4 line 9: 9 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/wenhao/bytecode/MovingAverage; public void submit(double); descriptor: (D)V flags: ACC_PUBLIC Code: stack=5, locals=3, args_size=2 0: aload_0 1: dup 2: getfield #2 // Field count:I (获取count变量的值,并压入操作数栈) 5: iconst_1 // 将int类型常量1(对应LocalVariableTable.Slot=1这个槽位)压入【操作数栈】 6: iadd // 将栈顶元素弹出栈,执行int类型的加法,结果入栈 7: putfield #2 // Field count:I 给count赋予add后的值 10: aload_0 11: dup 12: getfield #3 // Field sum:D 15: dload_1 // 从【局部变量1】中装载double类型值入栈 16: dadd 17: putfield #3 // Field sum:D 20: return LineNumberTable: line 12: 0 line 13: 10 line 14: 20 LocalVariableTable: Start Length Slot Name Signature 0 21 0 this Lcom/wenhao/bytecode/MovingAverage; 0 21 1 value D MethodParameters: Name Flags value public double getAvg(); descriptor: ()D flags: ACC_PUBLIC Code: stack=4, locals=1, args_size=1 0: iconst_0 1: aload_0 2: getfield #2 // Field count:I 5: if_icmpne 13 //i(int)cmp(compare)ne(not equal) 如果符合条件则跳到 13: aload_0 8: aload_0 9: getfield #3 // Field sum:D 12: dreturn 13: aload_0 14: getfield #3 // Field sum:D 17: aload_0 18: getfield #2 // Field count:I 21: i2d // int 转 double 22: ddiv 23: dreturn LineNumberTable: line 17: 0 line 18: 8 line 20: 13 LocalVariableTable: Start Length Slot Name Signature 0 24 0 this Lcom/wenhao/bytecode/MovingAverage; StackMapTable: number_of_entries = 1 frame_type = 13 /* same */ } SourceFile: "MovingAverage.java" ``` ```java package com.wenhao.bytecode; public class LocalVariableTest { public static void main(String[] args) { MovingAverage ma = new MovingAverage(); int num1 = 5; int num2 = 6; ma.submit(num1); ma.submit(num2); double avg = ma.getAvg(); } } ``` ```c++ Classfile /Users/liuwenhao/code/jvm-learn/target/classes/com/wenhao/bytecode/LocalVariableTest.class Last modified 2021-2-2; size 725 bytes MD5 checksum ae61b08f77b845dbf98bae3710975045 Compiled from "LocalVariableTest.java" public class com.wenhao.bytecode.LocalVariableTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #7.#29 // java/lang/Object."":()V #2 = Class #30 // com/wenhao/bytecode/MovingAverage #3 = Methodref #2.#29 // com/wenhao/bytecode/MovingAverage."":()V #4 = Methodref #2.#31 // com/wenhao/bytecode/MovingAverage.submit:(D)V #5 = Methodref #2.#32 // com/wenhao/bytecode/MovingAverage.getAvg:()D #6 = Class #33 // com/wenhao/bytecode/LocalVariableTest #7 = Class #34 // java/lang/Object #8 = Utf8 #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 Lcom/wenhao/bytecode/LocalVariableTest; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 ma #20 = Utf8 Lcom/wenhao/bytecode/MovingAverage; #21 = Utf8 num1 #22 = Utf8 I #23 = Utf8 num2 #24 = Utf8 avg #25 = Utf8 D #26 = Utf8 MethodParameters #27 = Utf8 SourceFile #28 = Utf8 LocalVariableTest.java #29 = NameAndType #8:#9 // "":()V #30 = Utf8 com/wenhao/bytecode/MovingAverage #31 = NameAndType #35:#36 // submit:(D)V #32 = NameAndType #37:#38 // getAvg:()D #33 = Utf8 com/wenhao/bytecode/LocalVariableTest #34 = Utf8 java/lang/Object #35 = Utf8 submit #36 = Utf8 (D)V #37 = Utf8 getAvg #38 = Utf8 ()D { public com.wenhao.bytecode.LocalVariableTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/wenhao/bytecode/LocalVariableTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=6, args_size=1 0: new #2 // class com/wenhao/bytecode/MovingAverage 3: dup 4: invokespecial #3 // Method com/wenhao/bytecode/MovingAverage."":()V 7: astore_1 8: iconst_5 // 将int类型的常量5压入【操作数栈】 9: istore_2 // 将int类型的值存入【槽位为2的局部变量,也就是num1】 10: bipush 6 // 将int类型的常量6压入【操作数栈】 12: istore_3 // 将int类型的值存入【槽位为3的局部变量,也就是num2】 13: aload_1 14: iload_2 15: i2d 16: invokevirtual #4 // Method com/wenhao/bytecode/MovingAverage.submit:(D)V 19: aload_1 20: iload_3 21: i2d 22: invokevirtual #4 // Method com/wenhao/bytecode/MovingAverage.submit:(D)V 25: aload_1 26: invokevirtual #5 // Method com/wenhao/bytecode/MovingAverage.getAvg:()D 29: dstore 4 31: return LineNumberTable: line 5: 0 line 6: 8 line 7: 10 line 8: 13 line 9: 19 line 10: 25 line 11: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 args [Ljava/lang/String; 8 24 1 ma Lcom/wenhao/bytecode/MovingAverage; 10 22 2 num1 I 13 19 3 num2 I 31 1 4 avg D MethodParameters: Name Flags args } SourceFile: "LocalVariableTest.java" ``` ### 算数操作与类型转换 | | add(+) | Sub(-) | Mult(*) | divide(/) | remainder(%) | negate(-()) | | ------ | ------ | ------ | ------- | --------- | ------------ | ----------- | | int | iadd | isub | imult | idiv | irem | ineg | | long | ladd | lsub | lmult | ldiv | lrem | lneg | | float | fadd | fsub | fmult | fdiv | frem | fneg | | Double | dadd | dsub | dmult | ddiv | drem | dneg | | | int | long | float | double | byte | char | short | | ------ | ---- | ---- | ----- | ------ | ---- | ---- | ----- | | int | - | i2l | i2f | i2d | i2b | i2c | i2s | | long | l2i | - | l2f | l2d | - | - | - | | float | f2i | f2l | - | f2d | - | - | - | | double | d2i | d2l | d2f | - | - | - | - | 一个完整的循环控制 ``` package com.wenhao.bytecode; public class ForLoopTest { private static int[] numbers = {1, 6, 8}; public static void main(String[] args) { MovingAverage movingAverage = new MovingAverage(); for (int number : numbers) { movingAverage.submit(number); } double avg = movingAverage.getAvg(); } } ``` ```c Classfile /Users/liuwenhao/code/jvm-learn/target/classes/com/wenhao/bytecode/ForLoopTest.class Last modified 2021-2-2; size 873 bytes MD5 checksum 7c7aef5476c1df992aacf86952b9e4b9 Compiled from "ForLoopTest.java" public class com.wenhao.bytecode.ForLoopTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #8.#36 // java/lang/Object."":()V #2 = Class #37 // com/wenhao/bytecode/MovingAverage #3 = Methodref #2.#36 // com/wenhao/bytecode/MovingAverage."":()V #4 = Fieldref #7.#38 // com/wenhao/bytecode/ForLoopTest.numbers:[I #5 = Methodref #2.#39 // com/wenhao/bytecode/MovingAverage.submit:(D)V #6 = Methodref #2.#40 // com/wenhao/bytecode/MovingAverage.getAvg:()D #7 = Class #41 // com/wenhao/bytecode/ForLoopTest #8 = Class #42 // java/lang/Object #9 = Utf8 numbers #10 = Utf8 [I #11 = Utf8 #12 = Utf8 ()V #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 this #17 = Utf8 Lcom/wenhao/bytecode/ForLoopTest; #18 = Utf8 main #19 = Utf8 ([Ljava/lang/String;)V #20 = Utf8 number #21 = Utf8 I #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 movingAverage #25 = Utf8 Lcom/wenhao/bytecode/MovingAverage; #26 = Utf8 avg #27 = Utf8 D #28 = Utf8 StackMapTable #29 = Class #23 // "[Ljava/lang/String;" #30 = Class #37 // com/wenhao/bytecode/MovingAverage #31 = Class #10 // "[I" #32 = Utf8 MethodParameters #33 = Utf8 #34 = Utf8 SourceFile #35 = Utf8 ForLoopTest.java #36 = NameAndType #11:#12 // "":()V #37 = Utf8 com/wenhao/bytecode/MovingAverage #38 = NameAndType #9:#10 // numbers:[I #39 = NameAndType #43:#44 // submit:(D)V #40 = NameAndType #45:#46 // getAvg:()D #41 = Utf8 com/wenhao/bytecode/ForLoopTest #42 = Utf8 java/lang/Object #43 = Utf8 submit #44 = Utf8 (D)V #45 = Utf8 getAvg #46 = Utf8 ()D { public com.wenhao.bytecode.ForLoopTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/wenhao/bytecode/ForLoopTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=6, args_size=1 0: new #2 // class com/wenhao/bytecode/MovingAverage 3: dup 4: invokespecial #3 // Method com/wenhao/bytecode/MovingAverage."":()V 7: astore_1 8: getstatic #4 // Field numbers:[I 11: astore_2 12: aload_2 13: arraylength 14: istore_3 15: iconst_0 16: istore 4 18: iload 4 20: iload_3 21: if_icmpge 43 24: aload_2 25: iload 4 27: iaload 28: istore 5 30: aload_1 31: iload 5 33: i2d 34: invokevirtual #5 // Method com/wenhao/bytecode/MovingAverage.submit:(D)V 37: iinc 4, 1 40: goto 18 43: aload_1 44: invokevirtual #6 // Method com/wenhao/bytecode/MovingAverage.getAvg:()D 47: dstore_2 48: return LineNumberTable: line 6: 0 line 7: 8 line 8: 30 line 7: 37 line 10: 43 line 11: 48 LocalVariableTable: Start Length Slot Name Signature 30 7 5 number I 0 49 0 args [Ljava/lang/String; 8 41 1 movingAverage Lcom/wenhao/bytecode/MovingAverage; 48 1 2 avg D StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 18 locals = [ class "[Ljava/lang/String;", class com/wenhao/bytecode/MovingAverage, class "[I", int, int ] stack = [] frame_type = 248 /* chop */ offset_delta = 24 MethodParameters: Name Flags args static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: iconst_3 1: newarray int 3: dup 4: iconst_0 5: iconst_1 6: iastore 7: dup 8: iconst_1 9: bipush 6 11: iastore 12: dup 13: iconst_2 14: bipush 8 16: iastore 17: putstatic #4 // Field numbers:[I 20: return LineNumberTable: line 4: 0 } SourceFile: "ForLoopTest.java" ``` ### 方法调用的指令 invokestatic,顾名思义,这个指令用于调用某个类的静态方法,这是方法调用指令中最快的一个。 invokespecial,用来调用构造函数,但也可以用于调用同一个类的private方法,以及可见的超类方法。 invokevitual,如果是具体类型的目标对象,invokevirtual用于调用公共,受保护和package级的私有方法。 invokeinterface,当通过接口引用来调用方法时,将会编译为invokeinterface指令。 invkedynamic,JDK7新增的指令,是实现“动态类型语言”(Dynamically Typed Language)支持儿进行的升级改进,同时也是JDK8之后支持lambda表达式的实现基础。 # JVM 类加载器 ## 类的生命周期 1. 加载(Loading):查找和导入Class文件 1. 通过一个类的全限定名获取定义此类的二进制字节流 2. 将这个字节流所代表的静态存储结构化为方法区的运行时数据结构 3. 在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口 2. 连接:将已经读入内存的类的二进制数据合并到JVM运行环境中。 1. 验证(Verification):主要包括 - 类文件结构检查:按照JVM规范规定的类文件结果进行验证; - 元数据验证:对字节码描述对信息进行语义分析,保证其符合Java语言规范要求; - 字节码验证:通过对数据流和控制流进行分析,确保程序语义是合法和符合逻辑。这里主要对方法体进行校验。 - 符号引用验证:对类自身以外的信息,也就是常量池中的各种符号引用,进行匹配校验。 2. 准备(Perparation):为类的静态变量分配内存,并将其初始化为默认值 3. 解析(Resolution):把常量池中的符号引用解析为直接引用(直接指向目标的指针、相对偏移量、或者能间接定位到目标的句柄,是和虚拟机实现相关),主要针对:类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用限定符 3. 初始化(Initializtion):类的静态变量赋值、或者说是执行类构造器(不是实例构造器)方法的过程 1. 如果类还没加载和连接,就先加载和连接 2. 如果类存在父类,且父类还没初始化,就先初始化父类 3. 如果类中存在初始化语句,就执行执行这些初始化 4. 如果是接口的话 - 初始化一个类的时候,并不会先初始化实现的接口 - 初始化一个接口时,并不会初始化它的父接口 - 只有当程序首次使用接口里面的变量或者时调用接口方法的时候,才会导致接口初始化 5. 调用Classloader类的loadClass方法来装载一个类,并不会初始化这个类,因为这不是对类的主动使用。 4. 使用(Using) 5. 卸载(Unloading) ![](.README_images/8f8d4b19.png) ## 类的加载时机 Java程序对类的使用方式分为:主动使用和被动使用,JVM必须在每个类或接口“首次主动使用”时才初始化它们;被动使用类不会导致类的初始化,主动使用的情况: 1. 当虚拟机启动时,初始化用户指定的主类,就是启动执行的main方法所在的类; 2. 当遇到用以新建目标类实例的new指令时,初始化new指令的目标类,就是new一个类的时候要初始化; 3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类; 4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类; 5. 子类的初始化会触发父类的初始化 6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化; 7. 使用反射API对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要么时已经有实例了,要么时静态方法,都需要初始化; 8. 当初次调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类。 不会初始化(可能会加载) 1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。 2. 定义对象数组,不会触发该类的初始化。 3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。(public `final` static String msg = "msg") 4. 通过类名获取Class对象,不会触发类的初始化,Hello.class不会让Hello类初始化。 5. 通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数时告诉虚拟机,是否要对类进行初始化。Class.forName("jvm.Hello")默认会加载Hello类。 6. 通过ClassLoader默认的loadClass方法,也不会触发初始化动作(加载了,但是不会初始化)。 ## 类装载器ClassLoader 在装载(Load)阶段,其中的第一步:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的。 ### ClassLoader分类 - Bootstrap ClassLoader 负责加载$JAVA_HOME中jre/lib/rt.jar里所有class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。 - Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。 - App ClassLoader 负责加载classpath中指定的jar包及-Djava.class.path所指定目录下的类和jar包。 - Custom ClassLoader 通过java.lang.classLoader 的子类自定义加载class,属于应用程序根据自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。 ![](.README_images/af29b911.png) ### 加载原则 ​ 检查某个类是否己经加载:顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个ClassLoader已加载,就视为已加载,保证此类只加载过一次。 双亲委派机制 定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才会自己去加载。 优势:java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,java中的Object类,它存放在rt.jar之中,无论哪个类加载器要加载这个类,最终都会委托给处于模型最顶端的启动类进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己去加载的话,那么系统中会存在不同的Object类。 破坏:可以继承ClassLoader类,然后重写其中的loadClass方法。 加载器的特点: 1. 双亲委派 2. 负责依赖 3. 缓存加载 自定义ClassLoader ```java package com.wenhao.classloader; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class HelloClassLoader extends ClassLoader { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { new HelloClassLoader().findClass("com.wenhao.bytecode.HelloDemo").newInstance(); } @Override protected Class findClass(String name) throws ClassNotFoundException { String myPath = "file:///Users/liuwenhao/code/jvm-learn/target/classes/com/wenhao/bytecode/HelloDemo.class"; byte[] cLassBytes = null; Path path = null; try { path = Paths.get(new URI(myPath)); cLassBytes = Files.readAllBytes(path); } catch (IOException | URISyntaxException e) { e.printStackTrace(); } return defineClass(name, cLassBytes, 0, cLassBytes.length); } } ``` # 内存结构 JVM内存整体结构 ![](.README_images/757131d9.png) ### PC寄存器 #### 定义 Program Counter Registry 程序计数器(寄存器) #### 作用 程序计数器的作用就是记住下一条jvm指令的执行地址。 #### 特点 - 线程私有 - 不会存在内存溢出 ### 虚拟机栈 ![](.README_images/3c72a717.png) ​ 栈的数据结构特点先进后出。虚拟机栈就是线程运行需要的内存空间,虚拟机栈由一个或者多个栈帧组成。栈帧是每个方法运行时需要的内存空间(局部变量、操作数栈(java没有寄存器,所有参数传递使用操作数栈)、常量指针、动态连接、方法返回值等)。每次方法调用创建一个栈帧,并压入栈,退出方法时,修改栈顶指针就可以吧栈帧中的内容销毁。栈帧是一个逻辑上的概念,具体的大小在一个方法编写完成后就基本上确定了。比如返回值需要多大的空间存储,每个局部变量需要对应的地址空间,此外还有给指令使用的操作数栈以及class指针(标识这个栈帧对应的是哪个类的方法,指向非堆里的Class对象) ​ 局部变量表存放了编译期可知的各种类型的基本数据类型和引用类型,每个slot存放32位数据,long、double占两个槽位。 #### 定义 Java Virtual Machine Stacks (java 虚拟机栈) - 每启动一个线程,JVM就会在栈空间分配对应的线程栈,比如1MB空间(-Xss1m)。线程栈也叫Java方法栈。如果使用JNI方法,则会分配一个单独的本地方法栈(Native Stack)。 - 线程执行过程中,一般会有多个方法组成调用栈(Stack Trace),比如A调用B,B调用C。。每执行到一个方法,就会创建对应的栈帧(Frame)。 - 每个线程只能有一个活动栈帧,对应着当前执行的那个方法。 #### 问题 - 垃圾回收是否涉及栈内存? 出栈内存自动释放,不需要垃圾回收 - 栈内存分配越大越好吗? 通过 -Xss size指定栈的大小。linux默认是1024KB。意为为每个虚拟机栈划分1M的内存。由于内存大小是固定的。假设是200M,这时可以支持200个虚拟机栈。如果给每个虚拟机栈划分2M的内存,那么只能支持100个虚拟机栈。从而影响线程数。所以不设置过大的栈内存,一般使用默认的即可。 - 方法内的局部变量是否线程安全? ```java package com.example; public class WenhaoTest { public static void main(String[] args) { } // sb 线程安全 public static void m1() { StringBuilder sb = new StringBuilder(); sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString()); } // sb 线程不安全 public static void m2(StringBuilder sb) { sb.append(1); sb.append(2); sb.append(3); System.out.println(sb.toString()); } // sb 线程不安全 public static StringBuilder m3() { StringBuilder sb = new StringBuilder(); sb.append(1); sb.append(2); sb.append(3); return sb; } } ``` 如果方法内局部变量没有逃离方法的作用范围,它就是线程安全的 如果是局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全。 #### 栈内存溢出 - 栈帧过多导致栈内存溢出 栈的大小是固定的。但是栈帧越来越多就会导致栈内存溢出(方法递归调用) - 栈帧过大导致栈内存溢出 ```java /** * 演示栈内存溢出 * -Xss 256k */ public class WenhaoTest { private static int count; public static void main(String[] args) { try { m1(); } catch (Throwable e) { e.printStackTrace(); // 打印递归次数 System.out.println(count); } } private static void m1() { count++; m1(); } } ``` ```java package com.example; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; import java.util.List; /** * 演示栈内存溢出 * -Xss 256k */ public class WenhaoTest { public static void main(String[] args) throws JsonProcessingException { Dept dept = new Dept(); dept.setName("Market"); Emp emp1 = new Emp(); emp1.setName("zhangsan"); emp1.setDept(dept); Emp emp2 = new Emp(); emp1.setName("lisi"); emp1.setDept(dept); dept.setEmps(Arrays.asList(emp1, emp2)); // {name: 'Market', emps: [{name:'zhangsan', dept: {name:'', emps:[{}]}}]} ObjectMapper objectMapper = new ObjectMapper(); System.out.println(objectMapper.writeValueAsString(dept)); } } class Emp { private String name; // 解决循环依赖关系 @JsonIgnore private Dept dept; public String getName() { return name; } public void setName(String name) { this.name = name; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } } class Dept { private String name; private List emps; public String getName() { return name; } public void setName(String name) { this.name = name; } public List getEmps() { return emps; } public void setEmps(List emps) { this.emps = emps; } } ``` #### 线程运行诊断 案例1:cpu占用过多 - top / top -p pid -H 查看那个进程占用cpu过多 - ps H -eo pid,tid,%cpu |grep 进程ID 定位是哪个线程对CPU占用过高 - jstack 进程ID > 1.txt - printf “%x” 十进制的线程id(拿到线程16进制的进程ID去1.txt文件看进程状态) 案例2:程序运行很长时间没有结果(死循环、死锁) jstack 进程ID> jstack.txt ```java package com.wenhao.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class CpuController { /** * 模拟死循环 * @return */ @GetMapping("loop") public List loop(){ String data = "{\"data\":[{\"partnerid\":]"; return getPartneridsFromJson(data); } /** * 模拟死锁 * @return */ @GetMapping("deadlock") public String deadlock(){ Object o1 = new Object(); Object o2 = new Object(); new Thread(()->{ synchronized (o1) { try {Thread.sleep(1000);}catch(Exception e) {} synchronized(o2) { System.out.println("Thread1 over"); } } }).start(); new Thread(()->{ synchronized (o2){ try {Thread.sleep(1000);}catch(Exception e) {} synchronized(o1) { System.out.println("Thread2 over"); } } }).start(); return "deadlock"; } public static List getPartneridsFromJson(String data){ //{\"data\":[{\"partnerid\":982,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":983,\"count\":\"10000\",\"cityid\":\"11\"},{\"partnerid\":984,\"count\":\"10000\",\"cityid\":\"11\"}]} //上面是正常的数据 List list = new ArrayList(2); if(data == null || data.length() <= 0){ return list; } int datapos = data.indexOf("data"); if(datapos < 0){ return list; } int leftBracket = data.indexOf("[",datapos); int rightBracket= data.indexOf("]",datapos); if(leftBracket < 0 || rightBracket < 0){ return list; } String partners = data.substring(leftBracket+1,rightBracket); if(partners == null || partners.length() <= 0){ return list; } while(partners!=null && partners.length() > 0){ int idpos = partners.indexOf("partnerid"); if(idpos < 0){ break; } int colonpos = partners.indexOf(":",idpos); int commapos = partners.indexOf(",",idpos); if(colonpos < 0 || commapos < 0){ //partners = partners.substring(idpos+"partnerid".length());//1 continue; } String pid = partners.substring(colonpos+1,commapos); if(pid == null || pid.length() <= 0){ //partners = partners.substring(idpos+"partnerid".length());//2 continue; } try{ list.add(Long.parseLong(pid)); }catch(Exception e){ //do nothing } partners = partners.substring(commapos); } return list; } } ``` ### 本地方法栈 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是虚拟机使用到的Native方法服务。 ### 堆 ​ 堆内存是所有线程公用的内存空间,JVM将Heap内存分为年轻代(Young generation)和老年代(Old generation,也叫Tenured)两部分。 ​ 年轻代还划分为3个内存池,新生代(Eden space)和存活区(Survivor space),在大部分GC算法中有2个存活区(s0,s1)默认Eden:s0:s1=8:1:1,在我们可以观察到的任一时刻,s0和s1总有一个是空的,但是s0和s1占有的空间比较小不会产生太多的空间浪费。总有一个空间是空的是因为垃圾回收过程会将A空间中有用的对象挪到B空间, 在吧A空间的对象清空。 ​ Non-Heap本质上还是Heap,只是一般不归GC管理,里面划分为3个内存池。 - Metaspace,之前叫持久代(永久代,Permanent generation),java8换了个名字叫Metaspace。 - CCS,Compressed Class Space,存放class信息和Metaspace有交叉 - Code Cache,存放JIT编译器编译后的本地机器代码 ![](.README_images/a4ea2897.png) #### 定义 Heap 堆 - Java堆用于存放应用系统创建的对象和数组,所有线程共享Java堆。 - java堆在运行期间动态分配内存大小,自动进行垃圾回收。 - java垃圾回收(GC)主要就是回收堆内存,对分代GC来说,堆也是分代的。 特点 - 它是线程共享的,堆中对象都需要考虑线程安全的问题 - 有垃圾回收机制 #### 堆内存溢出 ```java package com.example; import java.util.ArrayList; import java.util.List; /** * 演示堆内存溢出 * -Xmx8m */ public class WenhaoTest { public static void main(String[] args) { int i = 0; try { List list = new ArrayList<>(); String a = "hello"; while (true) { list.add(a); a = a + a; i++; } } catch (Throwable e) { e.printStackTrace(); System.out.println(i); } } } ``` #### 堆内存诊断 - jps 工具 查看当前系统有哪些java进程 - jmap 工具 查看堆内存占用情况 ```java package com.example.jvm; public class Demo_heap { public static void main(String[] args) throws InterruptedException { System.out.println("1"); Thread.sleep(30000); byte[] bytes = new byte[1024 * 1024 * 10]; // 10M System.out.println("2"); Thread.sleep(30000); bytes = null; System.gc(); System.out.println("3jps"); Thread.sleep(1000000L); } } ``` jps + jmap 查看 堆内存 ``` G:\workspace\study2020\demo>jps 31248 Demo_heap 13508 24248 RemoteMavenServer36 25448 Jps 19228 35788 Launcher // 时间点 1 G:\workspace\study2020\demo>jmap -heap 31248 Attaching to process ID 31248, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.141-b15 using thread-local object allocation. Parallel GC with 8 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 4253024256 (4056.0MB) NewSize = 88604672 (84.5MB) MaxNewSize = 1417674752 (1352.0MB) OldSize = 177733632 (169.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 66584576 (63.5MB) used = 6659056 (6.3505706787109375MB) free = 59925520 (57.14942932128906MB) 10.000898706631398% used From Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used To Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used PS Old Generation capacity = 177733632 (169.5MB) used = 0 (0.0MB) free = 177733632 (169.5MB) 0.0% used 3180 interned Strings occupying 260520 bytes. // 时间点 2 G:\workspace\study2020\demo>jmap -heap 31248 Attaching to process ID 31248, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.141-b15 using thread-local object allocation. Parallel GC with 8 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 4253024256 (4056.0MB) NewSize = 88604672 (84.5MB) MaxNewSize = 1417674752 (1352.0MB) OldSize = 177733632 (169.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 66584576 (63.5MB) used = 17144832 (16.3505859375MB) free = 49439744 (47.1494140625MB) 25.748954232283463% used From Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used To Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used PS Old Generation capacity = 177733632 (169.5MB) used = 0 (0.0MB) free = 177733632 (169.5MB) 0.0% used 3181 interned Strings occupying 260568 bytes. // 时间点 3 G:\workspace\study2020\demo>jmap -heap 31248 Attaching to process ID 31248, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.141-b15 using thread-local object allocation. Parallel GC with 8 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 4253024256 (4056.0MB) NewSize = 88604672 (84.5MB) MaxNewSize = 1417674752 (1352.0MB) OldSize = 177733632 (169.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 66584576 (63.5MB) used = 1331712 (1.27001953125MB) free = 65252864 (62.22998046875MB) 2.0000307578740157% used From Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used To Space: capacity = 11010048 (10.5MB) used = 0 (0.0MB) free = 11010048 (10.5MB) 0.0% used PS Old Generation capacity = 177733632 (169.5MB) used = 1339512 (1.2774581909179688MB) free = 176394120 (168.22254180908203MB) 0.7536626495091261% used 3167 interned Strings occupying 259576 bytes. ``` - jconsole 工具 ``` G:\workspace\study2020\demo>jconsole ``` 图形界面的,多功能的检测工具,可以连续检测 jvisualvm 点击堆 Dump 抓取 hprof快照 查询占用内存比较大的对象 分析对象的组成 案例源码: ```java package com.example.jvm; import java.util.ArrayList; import java.util.List; public class Demo1_13 { public static void main(String[] args) throws InterruptedException { List students = new ArrayList<>(); for (int i = 0; i < 200; i++) { students.add(new Student()); } Thread.sleep(1000000000L); } } class Student { private byte[] big = new byte[1024 * 1024]; } ``` #### 对象的内存布局 ​ 对象在内存中存储的布局(这里以HotSpot虚拟机位例来说),分为:对象头、实例数据和对齐填充。 ​ 对象头,包含两部分: - Mark Word:存储对象自身的运行数据,如:HashCode、GC分代年龄、锁状态标志等。 - 类型指针:对象指向它的类元数据的指针。 - 实例数据:真正存放对象实例数据的地方 - 对齐填充:这部分不一定存在,也没有什么特别的含义,仅仅是占位符。因为HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就需要对齐。 ##### 对象的访问定位 ​ 在JVM规范只规定了reference类型是一个指向对象的引用,但没有规定这个引用如何去定位、访问具体位置。因此对象的访问方式取决于JVM的实现,一般通过实现句柄或者指针这两种方式。 - 使用句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和元数据的地址,如下图: ![](.README_images/f5c49f15.png) - 使用指针:Java堆中会存放访问类元数据的地址,reference存储的就是对象的地址,如下图: ![](.README_images/05efa156.png) 使用句柄的优势在于在对象移动后只需修改句柄池中的指针,不需要修改reference。因为reference是直接引用的句柄池,句柄池的中指针再指向对象实例,正因为如此,运行的数据会稍慢些,因为进行了两次指针定位。使用指针的优势在于只需要一次指针定位速度快,Hotspot中使用的是采用指针的方式。 ### 方法区 #### 定义 ​ 方法区于Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的**类信息、常量、静态变量、即时编译器编译后的代码**等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。 ​ 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。方法区在JDK8中就是Metaspace,在JDK6或者7中就是Perm Space #### 方法区内存溢出 ```java package com.example.jvm; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Opcodes; /** * 演示元空间内存溢出 * -XX:MaxMetaspaceSize=8m 如果是jdk8之前的版本用的是永久代 -XX:MaxPermSize=8m java.lang.OutOfMemoryError: PermGen space */ public class Demo1_8 extends ClassLoader { // 用来加载类的二进制字节码 public static void main(String[] args) { int j = 0; try { Demo1_8 demo1_8 = new Demo1_8(); for (int i = 0; i < 10000; i++, j++) { // classwriter 用于生成类的二进制字节码 ClassWriter classWriter = new ClassWriter(0); // 版本号, public, 类名, 包名, 类的父类, 接口 classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); // 返回 byte[] byte[] code = classWriter.toByteArray(); // 加载类 demo1_8.defineClass("Class" + i, code, 0, code.length); } } finally { System.out.println(j); } } } ``` ### 运行时常量池 #### 定义 常量池Run-Time Constant Pool Class文件中除了有**类的版本、字段、方法、接口描述信息**外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。**运行时常量池(Runtime Constant Pool)是方法区的一部分**,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。 ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.example.jvm; public class HelloWorld { public HelloWorld() { } public static void main(String[] args) { System.out.println("hello world"); } } ``` ```java javap -v HelloWorld.class ``` ```c# Classfile /G:/workspace/study2020/demo/target/test-classes/com/example/jvm/HelloWorld.class Last modified 2020-4-27; size 565 bytes MD5 checksum 3e4924d19da8156d476a30190cf41cd1 Compiled from "HelloWorld.java" public class com.example.jvm.HelloWorld minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: // 常量池 #1 = Methodref #6.#20 // java/lang/Object."":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/example/jvm/HelloWorld #6 = Class #27 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/example/jvm/HelloWorld; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 HelloWorld.java #20 = NameAndType #7:#8 // "":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/example/jvm/HelloWorld #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V { public com.example.jvm.HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/jvm/HelloWorld; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 6: 0 line 7: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; } SourceFile: "HelloWorld.java" ``` ### StringTable 面试题: ```java package com.example.jvm; public class HelloWorld { public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "a" + "b"; String s4 = s1 + s2; String s5 = "ab"; String s6 = s4.intern(); // 问 System.out.println(s3 == s4); // false System.out.println(s3 == s5); // true System.out.println(s3 == s6); // true String x2 = new String("c") + new String("d"); String x1 = "cd"; String x3 = x2.intern(); // 将这个字符串对象尝试放入串池,如果有则不放入,如果没有则放入。会把串池中的对象返回 // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 System.out.println(x1 == x2); // 调换前 false 这时串池中已有 ab 所以不能放入串池 // 调换后 true // 如果是1.6 调换前 false 调换后 false System.out.println(x3 == x1); // x3是串池中的对象,所以这里是true } } ``` ```java package com.example.jvm; // --> StringTable[ "a", "b", "ab" ] hashtable结构,不能扩容 public class Demo1_22 { // 常量池中的信息,都会被加载到运行时常量池中,这时 a b ab 都是常量池中的符号,还没有变为java字符串对象 // ldc #2 会把a符号变为"a"字符串对象,将字符串对象放入StringTable(串池)中 // ldc #3 会把b符号变为"b"字符串对象,将字符串对象放入StringTable(串池)中 // ldc #4 会把ab符号变为"ab"字符串对象,将字符串对象放入StringTable(串池)中 public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; } } ``` ```java javap -v Demo1_22.class ``` ```c# Classfile /G:/workspace/study2020/demo/target/test-classes/com/example/jvm/Demo1_22.class Last modified 2020-4-27; size 692 bytes MD5 checksum 80dc30859e9b084311828cea9e313af6 Compiled from "Demo1_22.java" public class com.example.jvm.Demo1_22 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#29 // java/lang/Object."":()V #2 = String #30 // a #3 = String #31 // b #4 = String #32 // ab #5 = Class #33 // java/lang/StringBuilder #6 = Methodref #5.#29 // java/lang/StringBuilder."":()V #7 = Methodref #5.#34 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #8 = Methodref #5.#35 // java/lang/StringBuilder.toString:()Ljava/lang/String; #9 = Class #36 // com/example/jvm/Demo1_22 #10 = Class #37 // java/lang/Object #11 = Utf8 #12 = Utf8 ()V #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 this #17 = Utf8 Lcom/example/jvm/Demo1_22; #18 = Utf8 main #19 = Utf8 ([Ljava/lang/String;)V #20 = Utf8 args #21 = Utf8 [Ljava/lang/String; #22 = Utf8 s1 #23 = Utf8 Ljava/lang/String; #24 = Utf8 s2 #25 = Utf8 s3 #26 = Utf8 s4 #27 = Utf8 SourceFile #28 = Utf8 Demo1_22.java #29 = NameAndType #11:#12 // "":()V #30 = Utf8 a #31 = Utf8 b #32 = Utf8 ab #33 = Utf8 java/lang/StringBuilder #34 = NameAndType #38:#39 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #35 = NameAndType #40:#41 // toString:()Ljava/lang/String; #36 = Utf8 com/example/jvm/Demo1_22 #37 = Utf8 java/lang/Object #38 = Utf8 append #39 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #40 = Utf8 toString #41 = Utf8 ()Ljava/lang/String; { public com.example.jvm.Demo1_22(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 4: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/jvm/Demo1_22; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=5, args_size=1 0: ldc #2 // String a 2: astore_1 3: ldc #3 // String b 5: astore_2 6: ldc #4 // String ab 8: astore_3 9: new #5 // class java/lang/StringBuilder 12: dup 13: invokespecial #6 // Method java/lang/StringBuilder."":()V 16: aload_1 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: aload_2 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore 4 29: return LineNumberTable: line 10: 0 line 11: 3 line 12: 6 line 13: 9 line 14: 29 LocalVariableTable: Start Length Slot Name Signature 0 30 0 args [Ljava/lang/String; 3 27 1 s1 Ljava/lang/String; 6 24 2 s2 Ljava/lang/String; 9 21 3 s3 Ljava/lang/String; 29 1 4 s4 Ljava/lang/String; } SourceFile: "Demo1_22.java" ``` #### 特性 - 常量池中的字符串仅是符号,第一次用到才变成对象 - 利用串池的机制,来避免重复创建字符串对象 - 字符串拼接的原理是StringBuilder (1.8) - 可以使用intern方法, - 1.8 将这个字符串对象尝试放入串池,如果有则不放入,如果没有则放入。会把串池中的对象返回 - 1.6 将这个字符串对象尝试放入串池,如果有则不放入,如果没有则拷贝一份放入串池。会把串池中的对象返回 #### StringTable的位置 1.6中它存在于PerGen 永久代中的常量池中,在1.8中,它存在于堆 Heap中。这样的转变是为了更好的回收垃圾。 #### 验证StringTable所在位置 ```java package com.example.jvm; import java.util.ArrayList; import java.util.List; /** * 演示 StringTable位置 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit * 在jdk6下设置 -XX:MaxPermSize=10m */ public class Demo1_6 { public static void main(String[] args) { List list = new ArrayList<>(); int i = 0; try { for (int j = 0; j < 260000; j++) { // 将j作为字符串加入串池中,并且把字符串加入到list中防止被垃圾回收 list.add(String.valueOf(j).intern()); i++; } } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println(i); } } } // -XX:+UseGCOverheadLimit :This option is enabled,by default, and the parallel GC will throw an OutOfMemoryError if more than 98% of the total time is spent on garbage collection and less than 2% of the heap is recovered. ``` 1.8中运行: ```java java.lang.OutOfMemoryError: Java heap space ``` 1.6中运行: ```java java.lang.OutOfMemoryError: PermGen space ``` #### StringTable垃圾回收 ```java package com.example.jvm; /** * 演示 StringTable 垃圾回收 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc */ public class Demo1_7 { public static void main(String[] args) { int i = 0; try { } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(i); } } } ``` 执行结果: ```c# [GC (Allocation Failure) [PSYoungGen: 2048K->512K(2560K)] 2048K->902K(9728K), 0.0018141 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0 Heap PSYoungGen total 2560K, used 1852K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 2048K, 65% used [0x00000000ffd00000,0x00000000ffe4f310,0x00000000fff00000) from space 512K, 100% used [0x00000000fff00000,0x00000000fff80000,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 7168K, used 390K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) object space 7168K, 5% used [0x00000000ff600000,0x00000000ff661a18,0x00000000ffd00000) Metaspace used 3329K, capacity 4556K, committed 4864K, reserved 1056768K class space used 355K, capacity 392K, committed 512K, reserved 1048576K Disconnected from the target VM, address: '127.0.0.1:64028', transport: 'socket' SymbolTable statistics: Number of buckets : 20011 = 160088 bytes, avg 8.000 Number of entries : 13818 = 331632 bytes, avg 24.000 Number of literals : 13818 = 596704 bytes, avg 43.183 Total footprint : = 1088424 bytes Average bucket size : 0.691 Variance of bucket size : 0.691 Std. dev. of bucket size: 0.832 Maximum bucket size : 6 StringTable statistics: // 底层的实现是HashTable 数组 + 链表,数组的个数叫桶 buckets Number of buckets : 60013 = 480104 bytes, avg 8.000 // Number of entries : 1729 = 41496 bytes, avg 24.000 // 字符串对象个数 Number of literals : 1729 = 156360 bytes, avg 90.434 // 字符串常量个数 Total footprint : = 677960 bytes Average bucket size : 0.029 Variance of bucket size : 0.029 Std. dev. of bucket size: 0.170 Maximum bucket size : 2 ``` ```java package com.example.jvm; /** * 演示 StringTable 垃圾回收 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc */ public class Demo1_7 { public static void main(String[] args) { int i = 0; try { for (int j = 0; j < 100; j++) { String.valueOf(j).intern(); i++; } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(i); } } } ``` 执行结果: ```c# [GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->905K(9728K), 0.0014345 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 100 Heap PSYoungGen total 2560K, used 1831K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 2048K, 65% used [0x00000000ffd00000,0x00000000ffe4fe60,0x00000000fff00000) from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 7168K, used 417K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) object space 7168K, 5% used [0x00000000ff600000,0x00000000ff6686d0,0x00000000ffd00000) Metaspace used 3329K, capacity 4556K, committed 4864K, reserved 1056768K class space used 355K, capacity 392K, committed 512K, reserved 1048576K Disconnected from the target VM, address: '127.0.0.1:64657', transport: 'socket' SymbolTable statistics: Number of buckets : 20011 = 160088 bytes, avg 8.000 Number of entries : 13818 = 331632 bytes, avg 24.000 Number of literals : 13818 = 596704 bytes, avg 43.183 Total footprint : = 1088424 bytes Average bucket size : 0.691 Variance of bucket size : 0.691 Std. dev. of bucket size: 0.832 Maximum bucket size : 6 StringTable statistics: Number of buckets : 60013 = 480104 bytes, avg 8.000 Number of entries : 1829 = 43896 bytes, avg 24.000 // 字符串对象 +100 Number of literals : 1829 = 161160 bytes, avg 88.114 Total footprint : = 685160 bytes Average bucket size : 0.030 Variance of bucket size : 0.031 Std. dev. of bucket size: 0.175 Maximum bucket size : 2 ``` 执行结果: ```c# [GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->950K(9728K), 0.0016209 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] // 触发了GC 10000 Heap PSYoungGen total 2560K, used 2310K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 2048K, 88% used [0x00000000ffd00000,0x00000000ffec7880,0x00000000fff00000) from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 7168K, used 462K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) object space 7168K, 6% used [0x00000000ff600000,0x00000000ff673a08,0x00000000ffd00000) Metaspace used 3332K, capacity 4556K, committed 4864K, reserved 1056768K class space used 355K, capacity 392K, committed 512K, reserved 1048576K Disconnected from the target VM, address: '127.0.0.1:64780', transport: 'socket' SymbolTable statistics: Number of buckets : 20011 = 160088 bytes, avg 8.000 Number of entries : 13818 = 331632 bytes, avg 24.000 Number of literals : 13818 = 596704 bytes, avg 43.183 Total footprint : = 1088424 bytes Average bucket size : 0.691 Variance of bucket size : 0.691 Std. dev. of bucket size: 0.832 Maximum bucket size : 6 StringTable statistics: Number of buckets : 60013 = 480104 bytes, avg 8.000 Number of entries : 11652 = 279648 bytes, avg 24.000 // +9,923 并没有增加10000个字符串,因为出发了GC Number of literals : 11652 = 633064 bytes, avg 54.331 Total footprint : = 1392816 bytes Average bucket size : 0.194 Variance of bucket size : 0.208 Std. dev. of bucket size: 0.457 Maximum bucket size : 3 ``` #### StringTable 性能调优 ###### 调整 -XX:StringTableSize=桶个数 StringTable底层是HashTable,桶的个数 buckets多,元素就比较分散,hash碰撞的几率就减小,查找的速度就会变快。反之元素就比较集中,hash碰撞的几率就变大,导致链表较长,从而使查询速度变慢。 ```java package com.example.jvm; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; /** * -XX:PrintStringTableStatistics -XX:StringTableSize=20000 */ public class Demo1_24 { public static void main(String[] args) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) { String line = null; long start = System.nanoTime(); while (true) { line = reader.readLine(); if (line == null) { break; } line.intern(); } System.out.println("cost:" + (System.nanoTime() - start) / 1000000); } } } // StringTable size must be between 1009 and 23054843009213693051 ``` 得出的结论,buckets的值越大的话执行的速度越快。buckets越多,则它下边的链表长度就越短,节省了排除校验是否有重复字符串的时间。 ###### 考虑将字符串对象是否入池 字符串很多,但是有好多重复字符串,这时可以通过intern函数使字符串对象入池,从而减小字符串对内存的占用。 ### 直接内存 #### 定义 Direct Memory - 常见于NIO操作时,用于数据缓存 - 分配回收成本较高,但是读写性能高 - 不受JVM内存回收管理 划分出来的直接内存,java堆内存和系统内存都能读到。减少了缓存区之间复制的工作。 ### 堆栈方法去交互关系 ![](.README_images/2566e6dd.png) # JVM启动参数 ​ JVM中有很多的参数可以进行设置,这样可以让jvm在各种环境中能够高效的运行,绝大多数参数保持默认即可。 ​ JVM的参数类型分为3类,分别是: 1. 以-开头的**标准参数**,所有的JVM都要实现这些参数,并且向后兼容,例如:`-server` -D 设置系统属性,例如:`-Dfile.encoding=UTF-8` 2. -X 开头的**非标准参数**,基本都是传给JVM的,默认JVM实现这些参数的功能,但是并不保证所有JVM都满足,且不保证向后兼容。可以通过`java -X` 命令查看当前JVM支持的非标准参数。例如:`-Xmx8g` 3. 以-XX开头的**非稳定参数**,专门用于控制JVM的行为,跟具体的JVM实现有关,随时可能会在下个版本取消 - `-XX:+-Flags` 形式,+-是对布尔值进行开关。例如:`-XX:+UseG1GC` - `-XX:key=value` 形式,指定某个选项的值。例如:`-XX:MaxPermSize=256m` 同时可以根据功能划分为一下6类: 1. 系统属性参数 2. 运行模式参数 3. 堆内存设置参数 4. GC设置参数 5. 分析诊断参数 6. JavaAgent参数 ## 系统属性参数 用于设置系统属性,例如:`-Dmaven.test.skip=true` 在java代码里可以通过`System.getProperty("maven.test.skip")`获取到所设置的值。 ## 运行模式参数 1. `-server`:设置JVM使用server模式,特点是启动速度比较慢,但是运行性能和内存管理效率很高,适用于生产环境。在具有64位能力的JDK环境下将默认启用该模式,而忽略-client参数。 2. `-client`:JDK1.7之前在32位的x86机器上默认值是-client选项。设置JVM使用client模式,特点是启动速度较快,但是运行时性能和内存管理效率不高,通常用于客户端应用程序或者PC应用开发和调试。 3. `-Xint`:在解释模式(interprered mode)下运行,-Xint标记会强制JVM解释执行所有的字节码,当然这会降低运行速度,通常低10倍或者更多。 4. `-Xcomp`:-Xcomp参数与-Xint相反,JVM在第一次使用时会把所有的字节码编译成本地代码从而带来更大程度的优化。 5. `-Xmixed`:-Xmixed时混合模式,将解释模式和编译模式进行混合使用,由JVM自己决定,这是JVM的默认模式,也是推荐模式。使用java -version可以看到mixed mode等信息。 ## 堆内存参数 - `-Xmx`,指定最大堆内存,默认是物理内存的1/4。如-Xmx4g。这只是限制了Heap部分的最大值为4g。这个内存不包括栈内存,也不包括堆外使用的内存。(一般设置为机器内存的70%) - `-Xms`,指定堆内存空间的初始大小,默认是物理内存的1/64。如-Xms4g。而且指定的内存大小并不是操作系统实际分配的初始值而是GC先规划好,用到才分配。专用服务器上需要保持-Xms和-Xmx一致,否则应用刚启动可能就有好几个FullGC。当两者配置不一致时,堆内存扩展可能导致性能抖动。 - `-Xmn`,等价于-XX:NewSize,默认是整个堆的3/8,**使用G1垃圾收集器不应该设置该选项**,其他的某些场景下可以设置。官方建议设置为-Xmx的1/2~1/4。 - `-XX:MaxPermSize=size`,这是JDK1.7之间使用的。Java8默认允许的Meta空间无限大,次参数无效。 - `-XX:MaxMetaspaceSize=size`,Java8默认不限制Meta空间,一般不允许设置该选项。 - `-XX:MaxDirectMemorySize=size`,系统可以使用的最大堆外内存,这个参数跟`-Dsun.nioMaxDirectMemorySize`效果相同。 - `-Xss`:设置每个线程栈的字节数。例如:-Xss1m指定线程栈为1MB,与-XX:ThreadStackSize=1m等效。 ## GC相关参数 - `-XX:+UseG1GC`:使用G1垃圾回收器 - `-XX:+UseConcMarkSweepGC`:使用CMS垃圾回收器 - `-XX:+UseSerialGC`:使用串行垃圾回收器 - `-XX:+UseParallelGC`:使用并行垃圾回收器 - `-XX:+UnlockExperimentalVMOptions -XX:+UseZGC` // java 11+ - `-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC` //java12+ ## 分析诊断参数 - `-XX:+-HeapDumpOnOutOfMemoryError`选项,当OutOfMemoryError产生,即内存溢出(堆内存或持久代)时,会自动Dump堆内存。 - 示例:`java -XX:+HeapDumpOnOutOfMemoryError -Xms256m ConsumeHeap` - `-XX:HeapDumpPath`选项,与HeapDumpOnOutOfMemoryError搭配使用,指定内存溢出时Dump文件的目录 - 示例:`java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/ ConsumerHeap` 自动Dump的hprof文件会存储到/urs/local目录下 - `-XX:OnError`选项,发生致命错误时(fatal error)执行的脚本。 - `-XX:OnOutOfMemoryError`,抛出OutOfMemoryError错误执行的脚本 - `-XX:ErrorFile=fileName`,致命错误的日志文件名,绝对路径或者相对路径 - `-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1506`,远程调试 ### Trace跟踪(高版本说明)参数 https://docs.oracle.com/en/java/javase/13/docs/specs/man/java.html - 打印GC的简要信息: -Xlog:gc - 打印GC详细信息:-Xlog:gc* - 指定GC log位置,以文件输出:-Xlog:gc:*filename* - 每次GC后,打印堆信息:-Xlog:gc+heap=debug ![](.README_images/613ede2d.png) ## JavaAgent参数 ​ Agent是JVM中的一项黑科技,可以通过无侵入方式做很多事情,比如注入AOP代码,执行系统计算等等,权限非常大。 设置 agent 的语法如下: - -agentlib:libname[=options] 启用 native 方式的 agent, 参考 LD_LIBRARY_PATH 路径。 - -agentpath:pathname[=options] 启用 native 方式的 agent。 - -javaagent:jarpath[=options] 启用外部的 agent 库, 比如 pinpoint.jar 等等。 - -Xnoagent 则是禁用所有 agent。 以下示例开启 CPU 使用时间抽样分析: JAVA_OPTS="-agentlib:hprof=cpu=samples,file=cpu.samples.log" # 工具 JDK内置命令行工具 | 工具 | 简介 | | -------------- | ------------------------------------------------------------ | | java | Java应用的启动程序 | | javac | JDK内置的编译工具 | | javap | 反编译class文件的工具 | | javadoc | 根据Java代码和标准注释,自动生成相关的API说明文档 | | javah | JNI开发时,根据java代码生成需要的.h文件 | | extcheck | 检查某个jar文件和运行时拓展jar有没有版本冲突,很少使用 | | jdb | Java Debugger;可以调试本地和远端程序,属于JPDA中的一个demo实现,供其他调试器参考。开发时很少使用 | | jdeps | 探测class或jar包需要的依赖 | | jar | 打包工具,可以将文件和目录打包成为`.jar`文件;`.jar`文件本质就是zip文件,只是后缀不同。使用时按顺序对应好选项和参数即可 | | keytool | 安全证书和密钥的管理工具;(支持生成、导入、导出等操作) | | jarsigner | JAR文件签名和验签工具 | | policytool | 实际上这就是一款图形界面工具,管理本机的java安全策略 | | jps/jinfo | 查看java进程信息 | | jstat | 查看JVM内部gc相关信息 | | jmap | 查看heap或类占用空间统计 | | jstack | 查看线程信息 | | jcmd | 执行JVM相关分析命令(整合命令) | | Jrunscript/jjs | 执行js命令 | ## jps/jinfo ![](.README_images/113ffa9a.png) 基本上只能看到pid。可以加上参数`-mlvV`查看详细信息 ![](.README_images/b33bc6ba.png) jinfo pid查看进程详细信息,进程使用的jvm内存参数 ![](.README_images/c36c1648.png) ``` jinfo -flag MaxHeapSize pid(查看进程最大堆内存) jinfo -flag UseConcMarkSweepGC pid(查看垃圾回收器) jinfo -flag UseG1GC pid (查看是否开启G1GC) jinfo -flag UseParallelGC pid (查看是否开启ParallelGC) ``` ## jstat ``` Usage: jstat -help|-options jstat -