public class Test {
private int a;
public void f1(){
System.out.println(a);
}
}
编译后的class文件:
//每4位代表2个字节
cafe babe 0000 0034 001e 0a00 0600 1009
0011 0012 0900 0500 130a 0014 0015 0700
1607 0017 0100 0161 0100 0149 0100 063c
696e 6974 3e01 0003 2829 5601 0004 436f
6465 0100 0f4c 696e 654e 756d 6265 7254
6162 6c65 0100 0266 3101 000a 536f 7572
6365 4669 6c65 0100 0954 6573 742e 6a61
7661 0c00 0900 0a07 0018 0c00 1900 1a0c
0007 0008 0700 1b0c 001c 001d 0100 0454
6573 7401 0010 6a61 7661 2f6c 616e 672f
4f62 6a65 6374 0100 106a 6176 612f 6c61
6e67 2f53 7973 7465 6d01 0003 6f75 7401
0015 4c6a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 3b01 0013 6a61 7661 2f69
6f2f 5072 696e 7453 7472 6561 6d01 0007
7072 696e 746c 6e01 0004 2849 2956 0021
0005 0006 0000 0001 0002 0007 0008 0000
0002 0001 0009 000a 0001 000b 0000 001d
0001 0001 0000 0005 2ab7 0001 b100 0000
0100 0c00 0000 0600 0100 0000 0100 0100
0d00 0a00 0100 0b00 0000 2700 0200 0100
0000 0bb2 0002 2ab4 0003 b600 04b1 0000
0001 000c 0000 000a 0002 0000 0006 000a
0007 0001 000e 0000 0002 000f
魔数
文件开头的4个字节,标记文件的类型。class文件魔数为0xCAFFBABE
版本号
后续的4个字节代表jdk版本号。0000是编译器jdk版本的次版本号,0034转化为十进制是52,是主版本号,Java的版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一,也就是说,编译生成该class文件的jdk版本为1.8.0。
反编译字节码文件
对class文件进行反编译:
C:\Users\XXX\Desktop>javap -v -p Test.class
Classfile /C:/Users/XXX/Desktop/Test.class //Class文件当前所在位置
Last modified 2022-4-23; size 380 bytes //最后修改时间 文件大小
MD5 checksum 87f79e3030c2bb49f83ec8b3d6db0294 //MD5值
Compiled from "Test.java" //编译自哪个文件
public class Test //类的全限定名
minor version: 0 //jdk次版本号
major version: 52 //主版本号 ---> 1.8.0
flags: ACC_PUBLIC, ACC_SUPER //该类的访问标志
Constant pool: //常量池
#1 = Methodref #6.#16 // java/lang/Object."":()V
#2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Fieldref #5.#19 // Test.a:I
#4 = Methodref #20.#21 // java/io/PrintStream.println:(I)V
#5 = Class #22 // Test
#6 = Class #23 // java/lang/Object
#7 = Utf8 a
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 f1
#14 = Utf8 SourceFile
#15 = Utf8 Test.java
#16 = NameAndType #9:#10 // "":()V
#17 = Class #24 // java/lang/System
#18 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#19 = NameAndType #7:#8 // a:I
#20 = Class #27 // java/io/PrintStream
#21 = NameAndType #28:#29 // println:(I)V
#22 = Utf8 Test
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = Utf8 java/io/PrintStream
#28 = Utf8 println
#29 = Utf8 (I)V
{
private int a;
descriptor: I
flags: ACC_PRIVATE
public Test();
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 1: 0
public void f1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field a:I
7: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
10: return
LineNumberTable:
line 6: 0
line 7: 10
}
SourceFile: "Test.java"
访问标志
标志名称 | 含义 |
---|---|
ACC_PUBLIC | 是否为Public类型 |
ACC_FINAL | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 是否允许使用invokespecial字节码指令的新语义 |
ACC_INTERFACE | 标志这是一个接口 |
ACC_ABSTRACT | 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 |
ACC_SYNTHETIC | 标志这个类并非由用户代码产生 |
ACC_ANNOTATION | 标志这是一个注解 |
ACC_ENUM | 标志这是一个枚举 |
常量池
存放字面量(常量)和符号引用。
符号引用:
eg. Main方法调用Object类的构造函数
#1 = Methodref #6.#16 // java/lang/Object."":()V
#6 = Class #23 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#16 = NameAndType #9:#10 // "":()V
#23 = Utf8 java/lang/Object
eg. 类的属性a,类型为int
#3 = Fieldref #5.#19 // Test.a:I
#5 = Class #22 // Test
#7 = Utf8 a
#8 = Utf8 I
#19 = NameAndType #7:#8 // a:I
#22 = Utf8 Test
类型表:
标识字符 | 含义 |
---|---|
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L | 对象类型,以分号结尾,如Ljava/io/PrintStream; |
方法表
描述方法的类型、作用域
public void f1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3 // Field a:I
7: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
10: return
LineNumberTable:
line 6: 0
line 7: 10
stack: 最大操作数栈
locals:局部变量所需的存储空间,单位为Slot, Slot是虚拟机为局部变量分配内存时所使用的最小单位,为4个字节大小。方法参数(包括实例方法中的隐藏参数this),显示异常处理器的参数(try catch中的catch块所定义的异常),方法体中定义的局部变量都需要使用局部变量表来存放。值得一提的是,locals的大小并不一定等于所有局部变量所占的Slot之和,因为局部变量中的Slot是可以重用的
args_size:this
attribute_info:方法体内容,输出int类型a属性的值,没有返回值
LineNumberTable:源码行号与字节码行号(字节码偏移量)之间的对应关系
字段表
描述接口或类中声明的变量作用域、类型。
eg. Test类中a字段,int类型,private
private int a;
descriptor: I
flags: ACC_PRIVATE
属性表
描述字段表、方法表中特殊的属性
类索引、父类索引、接口索引
用于确定类的继承关系
加载
通过类的全限定名得到二进制字节流
将二进制字节流中静态代码块、静态方法转换为方法区运行时数据结构
在堆中生成代表这个列的Class类模版
ClassLoader
类加载器:
public class ClassLoader_ {
public static void main(String[] args) {
//获取应用程序类加载器
ClassLoader classLoader1 = ClassLoader.getSystemClassLoader();
//获取classLoader1的父类加载器
ClassLoader classLoader2 = classLoader1.getParent();
//获取classLoader2的父类加载器
ClassLoader classLoader3 = classLoader2.getParent();
System.out.println("应用程序类加载器:" +classLoader1);
System.out.println("扩展类加载器:" + classLoader2);
System.out.println("启动类加载器:" +classLoader3);
//获取本类的class对象
Class<ClassLoader_> aClass = ClassLoader_.class;
ClassLoader classLoader = aClass.getClassLoader();
System.out.println("本类的类加载器:" + classLoader);
/*
获取应用程序类能够加载的路径(类路径)
D:\jdk\jre\lib\charsets.jar;
D:\jdk\jre\lib\deploy.jar;
D:\jdk\jre\lib\ext\access-bridge-64.jar;
D:\jdk\jre\lib\ext\cldrdata.jar;
D:\jdk\jre\lib\ext\dnsns.jar;
D:\jdk\jre\lib\ext\jaccess.jar;
D:\jdk\jre\lib\ext\jfxrt.jar;
D:\jdk\jre\lib\ext\localedata.jar;
D:\jdk\jre\lib\ext\nashorn.jar;
D:\jdk\jre\lib\ext\sunec.jar;
D:\jdk\jre\lib\ext\sunjce_provider.jar;
D:\jdk\jre\lib\ext\sunmscapi.jar;
D:\jdk\jre\lib\ext\sunpkcs11.jar;
D:\jdk\jre\lib\ext\zipfs.jar;
D:\jdk\jre\lib\javaws.jar;
D:\jdk\jre\lib\jce.jar;
D:\jdk\jre\lib\jfr.jar;
D:\jdk\jre\lib\jfxswt.jar;
D:\jdk\jre\lib\jsse.jar;
D:\jdk\jre\lib\management-agent.jar;
D:\jdk\jre\lib\plugin.jar;
D:\jdk\jre\lib\resources.jar;
D:\jdk\jre\lib\rt.jar;
D:\java project\JVM\out\production\JVM;
D:\java project\JVM\lib\junit-4.10.jar;
D:\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar
*/
System.out.println(System.getProperty("java.class.path"));
/*
获取扩展类路径
D:\jdk\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext
*/
System.out.println(System.getProperty("java.ext.dirs"));
}
}
/*
应用程序类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
扩展类加载器:sun.misc.Launcher$ExtClassLoader@74a14482
启动类加载器:null
*/
Bootstrap ClassLoader,jdk\jre\lib下、核心类库rt.jar,所有java.*开头的类都能加载。java程序无法获取到启动类加载器,它是由C/C++编写的
Extension ClassLoader,jdk\jre\lib\ext下、由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类)
应用程序累加载器:
Application ClassLoader,加载用户类路径(ClassPath)所指定的类,程序默认的类加载器
注意:
1.当类加载器负责加载某个类时,该类中引用的其他类也交由该类加载器加载
2.加载过的类会被缓存,当加载某个类时,只有当缓存区不存在时,才会进行加载
3.以上三种类加载器只是从本地加载class文件,如果需要从其他地方加载class则必须自定义类加载器
验证
确保加载类的正确
0xCAFEBABE
开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型(-Xverifynone
参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间)
准备
为类的静态变量分配内存(在方法区中)并初始化默认值
八大基本类型赋默认值(0,false),引用类型赋null值
eg. static int a = 1;
在准备阶段a的值为默认零值,在初始化阶段才会真正赋初始值1
解析
将常量池中的符号引用转换为直接引用(真正的地址)
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量例如。在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。
直接引用:直接指向目标的指针、相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)或一个间接定位到目标的句柄
初始化
为类中的静态变量赋初始值
导致类初始化情况:
使用
访问方法区中的该类的字段方法,真正的数据位于堆中
卸载
JVM结束生命周期:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所有的类加载最终都应该被传递到顶层的启动类加载器中,只有当父加载器无法加载所需的类时,子加载器才会尝试自己去加载该类。
当AppClassLoader需要加载一个类时,它首先不会加载该类,而是委托给父类加载器ExtClassLoader,
而ExtClassLoader也不会加载该类,它也不会加载该类,委托给父类加载器BootstrapClassLoader,
当BootstrapClassLoader无法加载该类时,交给ExtClassLoader加载,
当ExtClassLoader无法加载该类时,交给AppClassLoader加载,
当AppClassLoader无法加载该类时,抛出ClassNotFoundException异常
双亲委派机制防止内存中出现和系统类相同的包名类名,保证java程序的安全
沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission),存在于不同域中的类文件就具有了当前域的全部权限。
PC寄存器存储下一条指令的地址,即将要执行的指令代码,是线程私有的,生命周期和线程相同,每个线程都有属于自己的,是唯一不会导致OOM的地方。
如果当前正在执行的是java方法,PC寄存器记录该指令地址,如果是本地方法(Native),则为undefine
每创建一个线程就会创建一个虚拟机栈,栈中是一个个栈帧,对应着方法调用,是线程私有的,生命周期与线程相同
栈不存在垃圾回收问题
每个方法执行就会入栈,方法结束就会出栈(包括正常执行return返回、抛出异常)
如果栈的大小固定,当线程请求分配栈容量超过最大容量会导致栈溢出(StackOverflowError),如果栈的大小可以动态增长,当分配的内存超出最大内存时会导致OOM(OutOfMemoryError),可以通过-Xss
来设置线程的最大栈空间
一个线程在一个时刻只有一个活动的栈帧(栈顶栈帧),就是当前栈帧,即当前执行的方法
局部变量表
:方法执行时完成参数传递,存储方法参数和定义在方法体内的局部变量(包括基本数据类型和对象引用),基本单位是Slot(槽),4个字节,当局部变量过了其作用域时该槽位可以重用节省资源
操作数栈
:也称为表达式栈,保存计算过程的中间结果和变量
HotSpot栈顶缓存
:将栈顶元素全部缓存到物理CPU寄存器中,降低对内存的读写,提高执行效率
动态链接
:指向运行时常量池中该栈帧所属方法的引用,用于将符号引用转换为调用方法的直接引用
方法返回地址
:存放调用该方法的 PC 寄存器的值,用于返回该方法被调用的位置,方法正常退出时,返回调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定的,栈帧中一般不会保存这部分信息,并且不会产生任何的返回值
附加信息
:与 Java 虚拟机实现相关的一些信息
本地方法接口
:本地方法就是一个 Java 调用非 Java 代码的接口,例如Unsafe类中的本地方法
本地方法栈
:用于管理登记本地方法,线程私有,在Hotspot中,本地方法栈和虚拟机栈合二为一
为进行高效的垃圾回收,JVM把堆内存逻辑上分为三部分
年轻代(Young Generation)
:新对象和没达到一定年龄的对象,包括三个部分:伊甸园区、幸存者0区和幸存者1区,默认比例为8:1:1
老年代(Old Generation)
:被长时间使用的对象,老年代的内存空间应该要比年轻代更大
元空间
:永久代(JDK8以前),JVM中方法区的实现
//JVM配置
//-Xms 设置初始化内存分配大小
//-Xmx 设置最大分配内存大小
//-XX:+PrintGCDetails 输出GC垃圾回收信息
//-XX:+HeapDump··· dump下程序的错误、异常 通过jprofiler工具定位具体的位置
public static void main(String[] args) {
//获取JVM能使用的最大内存 默认为电脑内存的1/4
long maxMemory = Runtime.getRuntime().maxMemory();
//获取JVM初始化时总内存 默认为电脑内存的1/64
long totalMemory = Runtime.getRuntime().totalMemory();
//-Xms1g -Xmx1g -XX:+PrintGCDetails 设置JVM初始化时总内存、最大内存为1G并输出GC垃圾回收信息
System.out.println("max = " + maxMemory + "字节, " + ((double)maxMemory / 1024 / 1024) + "M");
System.out.println("total = " + totalMemory + "字节, " + ((double)totalMemory / 1024 / 1024) + "M");
}
/*
(本机内存8G)
----------默认-----------------
max = 1873805312字节, 1787.0M
total = 126877696字节, 121.0M
---------配置后----------------
max = 1029177344字节, 981.5M
total = 1029177344字节, 981.5M
Heap
PSYoungGen total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3166K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 344K, capacity 388K, committed 512K, reserved 1048576K
*/
默认情况下新生代和老年代的比例是 1:2,可以通过 –XX:NewRatio
来配置
新生代中的 伊甸园区、幸存者0区和幸存者1区,默认比例为8:1:1,可以通过 -XX:SurvivorRatio
配置
所有线程共享的内存区域,也称为非堆(Non-Heap),存储存储类信息、常量池、静态变量、JIT编译后的代码等数据,只是一个概念,在Hotspot中实现方式为元空间
类型信息
:
对于每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation)存储
方法信息
:
对于每个方法存储:
运行时常量池
:JVM 为每个已加载的类型(类或接口)都维护一个动态的常量池
JVM参数类型分类:
1.标配参数
-version 显示版本号
-help 帮助文档
java -showversion 显示版本号
2.X参数
-Xint 解释执行
-Xcomp 编译执行
-Xmixed 混合模式执行
3.XX参数
Boolean类型:
-XX:+/-属性值(+ 代表开启 -代表关闭)
jps -l //查看java 进程
jinfo -flag 属性名称 进程号 //查看java进程对应属性的值
jinfo -flags 进程号 //查看java进程所有属性值
Non-default为属性默认值,Command line为修改后的值
KV类型:
-XX:属性key=属性value
注意:
-Xms和-Xmx属于XX参数类型,它们只是别名,全称分别为:-XX:initialHeapSize和 -XX:MaxHeapSize
查看JVM参数默认值:
java -XX:+PrintFlagsInitial [-version]
java -XX:+PrintFlagsFinal [-version] //主要查看修改更新的值
java -XX:+PrintCommandLineFlags [-version] //查看命令行参数
(在参数列表中,=
表示默认参数值,:=
表示修改过的参数值)
常用参数表:
参数名 | 解释 |
---|---|
-Xms | 初始化堆内存(-XX:InitialHeapSize 默认为物理内存的1/64) |
-Xmx | 最大分配内存(-XX:MaxHeapSize 默认为物理内存的1/4) |
-Xss | 单个栈的大小(-XX:ThreadStack 默认在512k~1024k) |
-Xmn | 新生代大小 |
-XX:MetaspaceSize | 元空间大小(默认20M左右 可以设置大一些避免OOM) |
-XX:+PrintGCDetails | 开启GC日志 |
-XX:SurvivorRatio | 新生代中伊甸园区、幸存者From区和幸存者To区所占空间的比例(默认为8,即8:1:1) |
-XX:NewRatio | 新生代和老年代比例(默认为2,即老年代占2新生代占1) |
XX:MaxTenuringThreshold | 从新生代进入老年代经历gc的次数(默认为15) |
JIT(Just-In-Time Compiler)是即时编译器,是一种提高程序运行效率的方法。通常程序有两种运行方式:
二八定律:20%的热点代码占据了程序80%的执行时间
热点代码有两类:
一般的代码解释执行,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会认为这些代码是热点代码。
为了提高热点代码的效率,会用JIT 把这些代码编译成机器码,并进行各层次优化。
hotspot中采用热点探测找到热点代码
方法调用计数器(invacation counter):用于统计方法被调用的次数
回边计数器(back edge Counter):用于统计一个方法中循环代码的执行次数,在字节码中遇到控制流向后跳转的指令称为回边,回边计数器统计的目的就是为了触发OSR(On Stack Replacement)栈上替换
编译
分析对象动态作用域:
栈上分配
:如果一个对象的指针永远不会逃逸,将堆分配转化为栈分配
同步省略
:也叫锁消除,如果一个对象只被一个线程访问,则对其的操作可以不考虑同步
public class SynchronizedClear_ {
public void test(){
Object o = new Object();
//给test()方法中的局部变量上锁,等同于未加锁
synchronized (o){
System.out.println("test....");
}
}
}
标量替换
:如果一个对象不会被外部访问,并且该对象可以被进一步分解时,JVM 不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替
缺点:逃逸分析相对耗时,无法保证逃逸分析的性能消耗一定能高于他的消耗
垃圾收集主要是针对堆和方法区进行,程序计数器、虚拟机栈和本地方法栈是线程私有的,线程执行完毕就会销毁,无需进行垃圾回收
方法区主要存放永久代对象回收率低,只要是对常量池的回收和对类的卸载
类卸载的条件:
类的所有实例都被回收,堆中不存在该类的实例
加载该类的ClassLoader
被回收
类的Class类对象没有在其他地方引用
Minor GC/Young GC
:轻GC,发生在新生代,会频繁执行,速度很快
Major GC/Old GC
:单独回收老年代的垃圾(CMS中使用)
Full GC
:回收整个java堆和方法区的垃圾
引用计数法(JVM不使用)
:
给每个对象设置一个计数器,当对象增加一个引用时计数器加一,引用失效计数器减一,只回收计数器你为0的对象。如果存在循环引用的情况,引用计数器不可能为0,无法对它们进行回收
为解决循环引用问题,使用可达性分析,通过GC Roots作为起始点,如果一个对象到GC Roots没有任何链
路可用则说明对象不可用会被视为垃圾
GC Roots是一个set集合,包括:
复制算法(新生代)
:
对于新生代区域,每次将Eden(伊甸园区)和From幸存者区中存活的对象复制到To幸存区之中,复制完成后,幸存区名字交换,在两个幸存区中始终保证有一个为空。占用内存空间,有一半的空间浪
标记清除算法(老年代)
:
对于老年代区域,对存活的对象进行标记,每次清除掉未标记的对象。但是标记影响效率,清除之后会产生大量不连续的内存碎片
标记清除整理算法(老年代)
:
在标记清除算法基础之上,将存活的对象移到一端,避免产生内存碎片。但是移动存活对象也影响效率
分代收集算法
:
对于堆中不同区域采取不同的方式:
jdk8垃圾回收器主要有6种:
只使用单个线程进行垃圾回收,会暂停用户线程,不适合服务器
-XX:+UseSerialGC
使用SerialGC(复制) + SerialOldGC(标记-整理)
DefNew:Default New Generation
Tenured:Old
是SerialGC新生代并行版本,多个线程共同工作,会暂停用户线程,适用于科学计算/大数据等弱交互场景
-XX:+UseParallelNewGC
使用ParNew(复制) + Serial Old(标记-整理)
ParNew:Parallel New Generation
Serial Old垃圾收集器已经过时被弃用
在新生代和老年代都是用并行回收
-XX:+UseParallelGC 或 -XX:UseParallelOldGC //可相互激活
PSYoungGen:Parallel Scavenge
ParOldGen:Parallel Old Generation
自适应调节:
吞吐量即用户代码运行时间 / (用户代码运行时间 + 垃圾收集时间),-XX:MaxGCPauseMills设置停顿时间,
-XX:ParallelGCThreads设置GC并发线程数,cpu > 8时设置为 5 / 8,cpu < 8时为实际个数
用户线程和垃圾回收线程可以同时执行,用户线程停顿低,适用于对响应时间有要求的场景
步骤:
优点:并发收集响应快
缺点:并发执行cpu压力大,会产生大量内存碎片
-XX:+UseConcMarkSweepGC //会激活-XX:+UseParNewGC
使用ParNew(复制) + CMS(并发标记清除) + Serial Old(作为CMS出错的后备收集器)
黑色:GCRoots不会是垃圾标记为黑色,如果扫描对象和它所直接引用的所有对象都被访问过则被标记为黑色
灰色:如果扫描对象的直接引用至少有一个没有被访问则被标价为灰色
白色:如果对象没有被访问则被标记为白色
由于CMS的并发标记过程不会暂停用户线程,用户线程可能修改对象引用漏标和错标的问题
针对错标问题,使用增量更新和原始快照解决
原始快照比增量更新效率高,因为不用在重新标记阶段再去遍历,但是也就可能会导致有更多的浮动垃圾
G1使用的就是原始快照,CMS使用的是增量更新
不在按照新生代、老年代分别回收,将堆内存分割成不同的**区域(Region)**然后并发的进行垃圾回收
每个区域会在新生代、老年代之间切换,标记整理之后使得空闲区域连接起来没有内存碎片
步骤:
优点:
G1回收器没有内存碎片,可以控制GC完成的时间
G1常用参数:
参数名 | 解释 |
---|---|
-XX:G1HeapRegionSize | 设置G1区域大小(必须为2的幂次方,在1MB~32MB) |
-XX:MaxGCPauseMills | 最大GC停顿时间(GC完成时间) |
-XX:InitiatingHeapOccupancyPercent | 堆内存占用多少时就触发GC(默认为45%) |
-XX:ConcGCThreads | 并发GC使用的线程数 |
-XX:G1ReservePercent | 设置作为空闲空间的预留内存百分比,避免空间溢出(默认为10%) |
垃圾回收器的选择