JVM
JVM基础知识
JDK、JRE、JVM
1)Jdk包括了Jre和Jvm,Jre包括了Jvm,Jdk是我们编写代码使用的开发工具包。
2)Jre是Java的运行时环境,他大部分都是C和C++语言编写的,他是我们在编译Java时所需要的基础的类库。
3)Jvm俗称Java虚拟机,他是Java运行环境的一部分,它虚构出来的一台计算机,再通过在实际的计算机上仿真模拟各种计算机功能来实现Java应用程序。
JVM基本参数
标准参数(一般默认)
-help:查看JVM帮助,使用java -help可以查看所有的标准参数
-version:输出产品版本并退出
-server:Server VM的初始堆空间大一些,使用并行垃圾回收器,JVM启动慢运行快。
-client:Client VM的初始堆空间比较小,使用串行垃圾回收器,JVM启动快运行慢。
JVM在启动的时候会根据硬件和操作系统自动选择使用Server还是Client类型的JVM。
32位操作系统:如果是Windows系统,不论硬件配置如何,都默认使用Client类型的JVM。 如果是其他操作系统上,机器配置有2GB以上的内存同时有2个以上CPU的话默认使用server模式,否则使用client模式。
64位操作系统:只有server类型,不支持client类型。
-X参数(非标准参数,随相应程序需要做改变)
使用java -X可以查看所有的非标准参数,如改变模式命令:
java -showversion -Xint JvmTest(可以查看java版本并继续)
-Xint:解释模式,会执行所有的字节码文件,会降低运行速度。
-Xcomp:编译模式,JVM会在第一次执行时,将所有字节码编译成本地代码,带来最大程度的优化。
-Xint与-Xcomp比较:都会有性能上的损失(Xint>Xcomp),-Xcomp没有让JVM启用JIT编译器(JIT可以对是否编译做判断)的所有功能;如果全部再次编译,对于只执行一次的代码,毫无意义。
-Xmixed:混合模式,解释模式与编译模式混合使用,由JVM自己决定,这是默认模式,也是推荐模式。
-XX参数(非标准参数,使用率比较高,用于JVM调优)
-XX:newSize
-XX:+UserSerialGC
参数使用的2种方式:boolean类型和非boolean类型,格式:
boolean类型:-XX:[+-]UserSerialGC:加表示启用,减表示禁用;
如:-XX:+DisableExplicitGC,表示禁用手动调用gc操作,也就是说调用System.gc()无效。非boolean类型:-XX:NewRatio=1,表示新生代和老年代的比值,如命令:
java ‐showversion ‐XX:+DisableExplicitGC JvmTest
-Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。
-Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。
适当的调整jvm的内存大小,可以充分利用服务器资源,让程序跑的更快。命令:
java ‐Xms512m ‐Xmx2048m JvmTest(JvmTest:java类编译后的class文件)
JVM运行参数
查看JVM的运行参数:
java -XX:+PrintFlagsFinal -version JvmTest[编译后的class文件名]
输出结果中如果值的操作符为=,表示设置的是默认值,如果为:=,表示设置的是修改后的值;
修改参数值:
java -XX:+PrintFlagsFinal -XX:+ZeroTLAB JvmTest[编译后的class文件名]
查看正在运行的java进程(比如一个tomcat):jps -l(这是小写的L)
tomcat进程名称:org.apache.catalina.startup.Bootstrap
查看正在运行的JVM参数:jinfo -flags <进程ID>
查看正在运行的指定参数:jinfo -flags <参数名(如MaxHeapSize)> <进程ID>
根据字节转换可以知道一些参数的值到底有多大
JVM内存查看
查看堆内存使用情况
查看堆内存使用情况:
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
查看class加载统计
jstat -class <进程ID>
一些参数的含义:
Loaded:加载class的数量
Bytes:所占用空间大小
Unloaded:未加载数量
Bytes:未加载占用空间
Time:时间
查看编译统计
jstat -compiler <进程ID>
一些参数的含义:
Compiled:编译数量
Failed:失败数量
Invalid:不可用数量
Time:时间
FailedType:失败类型
FailedMethod:失败的方法
垃圾回收统计
jstat -gc <进程ID> 1000 10(这个表示每一秒钟打印一次,一共打印10次)
一些参数的含义:
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:垃圾回收消耗总时间
查看更详细内存情况
查看内存使用情况:
jmap -heap <进程ID>
查看内存中对象的数量及大小:
jmap -histo <进程ID> | more
查看内存中活跃对象的数量及大小:
jmap -histo:live <进程ID> | more
返回对象(class name)说明:
返回参数 | 代表含义 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
Z | boolean |
[ | 数组,如[I表示int[] |
[L+类名 | 其他对象 |
将内存使用情况导出到文件中
导出的意义是可以查看JVM中对象的长度等信息,然后做出更好的调整,导出命令格式:
jmap -dump:format=b,file=dumpFileName <进程ID>
如:jmap -dump:format=b,file=D:/dump.dat 1400
JVM内存情况分析
通过jhat对dump文件进行分析
jhat -port <port> <file>
如:jhat -port 9999 D:/dump.dat
查看该文件方式:http://localhost:9999
执行文件情况查询语句:
通过mat工具对都dump文件进行分析
MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。 官网地址:https://www.eclipse.org/mat/ ,下载之后直接解压缩安装即可,双击启动程序:
点击file>>>open heap dump打开一个dump文件
第一个红框:列出内存中的对象,对象的数量和大小
第二个红框:列出最大的对象以及其依赖存活的对象
报表展示可能存在内存溢出的地方
模拟内存溢出
package com.study;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author: XDZY
* @date: 2020/08/30 13:11
* @description: 测试内存溢出
*/
public class JvmTest2 {
/**
* 向集合中输入100万个字符串,每个字符串由1000个uuid组成
*
* @param args
*/
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
String str = "";
for (int j = 0; j < 1000; j++) {
str = UUID.randomUUID().toString();
}
stringList.add(str);
}
System.out.println("ok");
}
}
设置内存大小,当内存溢出时在本地生成hprof文件,通过mat软件打开该文件即可分析问题
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
JVM程序线程查看
jstack的使用(查看JVM中程序线程)
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等。由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来。查看JVM中某个程序的所有线程执行情况:
jstack <pid>
线程的状态:
产生死锁时查看:
jps -l #查看java进程
jstack <pid> #查看问题产生原因
日志输出如下:
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.study.JvmTest3.lambda$main$1(JvmTest3.java:39)
- waiting to lock <0x00000000d64b6848> (a java.lang.Object)
- locked <0x00000000d64b6858> (a java.lang.Object)
at com.study.JvmTest3$$Lambda$2/990368553.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at com.study.JvmTest3.lambda$main$0(JvmTest3.java:24)
- waiting to lock <0x00000000d64b6858> (a java.lang.Object)
- locked <0x00000000d64b6848> (a java.lang.Object)
at com.study.JvmTest3$$Lambda$1/295530567.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
JVM调优分析工具
JDK自带了很多监控工具,都位于JDK的bin目录下,其中最常用的是jconsole和jvisualvm这两款视图监控工具。jconsole用于对JVM中的内存、线程和类等进行监控。jvisualvm是JDK自带的全能分析工具,可以分析内存快照、线程快照、程序死锁、监控内存的变化、gc变化等。
VisualVM工具的使用
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。VisualVM使用简单,几乎0配置,功能还是比较丰富的,几乎囊括了其它JDK自带命令的所有功能。
1、内存信息
2、线程信息
3、Dump堆(本地进程)
4、Dump线程(本地进程)
5、打开堆Dump,堆Dump可以用jmap来生成。
6、打开线程Dump
7、生成应用快照(包含内存信息、线程信息等等)
8、性能分析。CPU分析(各个方法调用时间,检查哪些方法耗时多),内存分析(各类对象占用的内存,检查哪些类占用内存多)
在jdk的安装目录的bin目录下,找到jvisualvm.exe,双击打开即可:
页面展示:
JVM进阶知识
JVM结构组成
JVM包含两个子系统和两个组件:两个子系统为类加载子系统、执行引擎,两个组件为运行时数据区、本地接口。流程 :首先通过编译器把Java代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
JVM字节码执行引擎
虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。虚拟机是一个相对于物理机的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎编译成机器码后才可在物理机上执行。
JVM类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的Java类型。程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载(loading)、连接(linking)、初始化(initialization)3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把Class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。类装载方式,有两种 :
隐式装载:程序在运行过程中当碰到通过new等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。
显式装载:通过class.forname()等方法,显式加载需要的类。
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到JVM中,至于其他类,则在需要的时候才加载,这当然就是为了节省内存开销。
类加载器类别
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器,主要有一下四种类加载器:
1)启动类加载器(Bootstrap ClassLoader):用来加载java核心类库,无法被java程序直接引用。
2)扩展类加载器(extensions class loader):它用来加载Java的扩展库。Java虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载Java类。
3)系统类加载器(system class loader):它根据Java应用的类路径(CLASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
4)用户自定义类加载器,通过继承java.lang.ClassLoader类的方式实现。
加载
加载指的是将类的Class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。类加载器,可以从不同来源加载类的二进制数据,比如本地Class文件、Jar包Class文件、网络Class文件等。类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口。
连接过程
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将Java类的二进制代码合并到JVM的运行状态之中)。类连接又可分为如下3个阶段:
验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池的符号引用替换为字节引用过程。
初始化
初始化阶段是执行类构造器
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
JVM运行时数据区
Java虚拟机在执行Java程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java虚拟机所管理的内存被划分为如下几个区域:
程序计数器
程序计数器(Program Counter Register):也可以把它叫做线程计数器(线程是不具备记忆功能,所以要借助计数进行线程的切换)。当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成。程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为线程私有的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
虚拟机栈
Java虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java虚拟机栈中创建一个栈帧(Stack Frame,栈帧就是Java虚拟机栈中的下一个单位)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
Java虚拟机是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。虚拟机栈中是有单位的,单位就是栈帧,一个方法一个栈帧。栈帧存储内容说明如下:
局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
操作数栈:操作数栈就是用来操作的,例如代码中有个i=6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去。
动态链接:假如我方法中,有个service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。
出口:出口是什呢,出口正常的话就是return,不正常的话就是抛出异常。
一个方法调用另一个方法,会创建很多栈帧,如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面。栈指向堆是指栈中要使用成员变量时,栈中不会存储成员变量,只会存储一个应用地址。递归的话也会创建多个栈帧,就是在栈中一直从上往下排下去。
本地方法栈
本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的。Native关键字修饰的方法是看不到的,Native方法的源码大部分都是C和C++的代码。他和栈很像,只不过是方法上带了native关键字的栈。它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法。native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。同理可得,本地方法栈中就是C和C++的代码。
堆
Java堆(Java Heap):Java虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存。堆在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,因此也被称为“GC堆”。从内存回收角度来看堆可分为:新生代和老生代。从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。根据Java虚拟机规范的规定,堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
方法区
方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。方法区是所有线程共享的内存区域,它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
堆栈的区别
对比 | JVM堆 | JVM栈 |
---|---|---|
物理地址 | 堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记清除,复制,标记压缩,分代(即新生代使用复制算法,老年代使用标记压缩)。 | 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的,所以性能快。 |
内存 | 堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定,一般堆大小远远大于栈。 | 栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。 |
存放的内容 | 堆存放的是对象的实例和数组,因此该区更关注的是数据的存储。 | 栈存放局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。 |
程序的可见度 | 堆对于整个应用程序都是共享、可见的。 | 栈只对于线程是可见的,所以也是线程私有,他的生命周期和线程相同。 |
JVM内存模型
jdk1.7的堆内存模型
区域 | 说明 |
---|---|
Young(新生代) | Young区被划分为三部分,Eden(ˈiːdn)区和两个大小严格相同的Survivor(sərˈvaɪvər)区,其中Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候,GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,在经过几次垃圾收集后,仍然存活于Survivor的对象将被移动到Tenured区间。 |
Tenured(老年代) | Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的Java对象)。 |
Perm(永久代) | Perm代主要保存class、method、filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError : PermGen space的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。 |
jdk1.8的堆内存模型
jdk1.8的内存模型是由2部分组成,年轻代 + 年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间,非堆内存)进行了替换。
需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。
废弃永久区原因(官网解释):移除永久代是为了融合HotSpot JVM与JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异java.lang.OutOfMemoryError:PermGen。基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。
新生代分区的意义
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次(默认15,可设置)Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1,这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生。
新生代与老年代堆空间比例
默认新生代与老年代的比例的值为1:2,该值可以通过参数–XX:NewRatio来指定 ,即:新生代占1/3的堆空间大小,老年代占2/3的堆空间大小。其中,新生代被细分为Eden和两个Survivor区域,Edem和俩个Survivor区域比例是8:1:1,可以通过参数–XX:SurvivorRatio来设定,但是JVM每次只会使用Eden和其中的一块Survivor区域来为对象服务,所以无论什么时候,总是有一块Survivor区域是空闲着的。
JVM垃圾回收
垃圾回收机制
概念 | 说明 |
---|---|
回收机制 | 在Java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。 |
回收时机 | 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。 |
GC | GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是可达的,哪些对象是不可达的。当GC确定一些对象为不可达时,GC就有责任回收这些内存空间。程序员可以手动执行System.gc()通知GC运行,但是Java语言规范并不保证GC一定会执行。 |
Minor GC(ˈmaɪnər) | 是新生代GC,指的是发生在新生代的垃圾收集动作。由于Java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快,一般采用复制算法回收垃圾。Eden区满时,触发MinorGC,即申请一个对象时,发现Eden区不够用,则触发一次MinorGC。 |
Major GC(ˈmeɪdʒər) | 是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多,可采用标记清楚法和标记整理法。Major GC触发通常是跟full GC是等价的,如永久代空间不足或执行System.gc()。 |
Full GC | 清理整个堆空间,包括年轻代和老年代和永久代。因为Full GC是清理整个堆空间,所以Full GC执行速度非常慢,在Java开发中最好保证少触发Full GC。 |
GC Roots | 虚拟机栈的栈帧的局部变量表所引用的对象、本地方法栈的JNI所引用的对象、方法区的静态变量和常量所引用的对象 |
可达对象 | 能与GC Roots构成连通图的对象,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可达的 |
JNI | JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要C&C++) |
垃圾回收算法
引用计数法(已淘汰)
1)原理:假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。
2)优点:实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember错误。区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
3)缺点:每次对象被引用时,都需要去更新计数器,有一点时间开销。浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。最大的缺点是无法解决循环引用问题。
/**
* 循环引用案例(引用计数算法最大缺陷)
* a、b计数都是为1,即使为空无法回收
*/
@Test
public void jvmTest() {
TestA a = new TestA();
TestB b = new TestB();
a.b = b;
b.a = a;
a = null;
b = null;
}
class TestA {
public TestB b;
}
class TestB {
public TestA a;
}
标记清除法(广泛使用)
1)原理:标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。标记:从根节点GC Roots开始标记引用的对象。清除:未被标记引用的对象就是垃圾对象,可以被清理。
上面这张图代表的是程序运行期间所有对象的状态,它们的标志位全部是0(也就是未标记,以下默认0就是未标记,1为已标记),假设这会儿有效内存空间耗尽了,JVM将会停止应用程序的运行并开启GC线程(不停止,引用会动态变化),然后开始进行标记工作,按照根搜索算法,标记完以后,对象的状态如下图。
可以看到,按照算法,所有从root对象可达的对象就被标记为了存活的对象,此时已经完成了第一阶段标记。接下来,就要执行第二阶段清除了,那么清除完以后,可以看到,没有被标记的对象将会回收清除掉,而被标记的对象将会留下,并且会将标记位重新归0。接下来就会唤醒停止的程序线程,让程序继续运行即可。
2)优点:可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。
3)缺点:效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。
标记压缩法/标记整理法
1)原理:标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。
2)优缺点:优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。
复制算法
1)原理:复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。
2)优点:在垃圾对象多的情况下,效率较高,清理后,内存无碎片。
3)缺点:在垃圾对象少的情况下,不适用,如:老年代内存。分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低。
分代算法
分代算法是根据回收对象的特点进行选择,在JVM中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片,所以一般在老年代使用标记压缩算法。分代处理流程:
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的1/3,老生代的默认占比是2/3。新生代使用的是复制算法,新生代里有3个分区:Eden、To Survivor、From Survivor,它们的默认占比是8:1:1,它的执行流程如下:
1)把Eden+From Survivor存活的对象放入To Survivor区;
2)清空Eden和From Survivor分区;
3)From Survivor和To Survivor分区交换,From Survivor变To Survivor,To Survivor变From Survivor。
4)每次在From Survivor到To Survivor移动时都存活的对象,年龄就+1,当年龄到达 15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代。
5)老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
垃圾回收器与内存分配
垃圾回收器概览
垃圾回收器是垃圾回收算法(标记清除法、标记整理法、复制算法、分代算法)的具体实现,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。
垃圾回收器类别说明
垃圾回收器 | 工作区域 | 回收算法 | 工作线程 | 用户线程并行 | 描述 |
---|---|---|---|---|---|
Serial | 新生带 | 复制算法 | 单线程 | 否 | Client模式下默认新生代收集器,简单高效。新生代单线程收集器,标记和清理都是单线程,优点是简单高效。 |
ParNew | 新生带 | 复制算法 | 多线程 | 否 | Serial的多线程版本,Server模式下首选, 可搭配CMS的新生代收集器。新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。 |
Parallel Scavenge | 新生带 | 复制算法 | 多线程 | 否 | 目标是达到可控制的吞吐量。新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景。 |
Serial Old | 老年带 | 标记整理 | 单线程 | 否 | Serial老年代版本,给Client模式下的虚拟机使用。 |
Parallel Old | 老年带 | 标记整理 | 多线程 | 否 | Parallel Scavenge老年代版本,吞吐量优先。 |
CMS(Concurrent Mark Sweep) | 老年代 | 标记清除 | 多线程 | 否 | 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。 |
G1(Garbage First) | 新生带 、老年带 | 标记整理、复制算法 | 多线程 | 是 | JDK1.9默认垃圾收集器。Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于标记整理算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。 |
串行垃圾回收器
串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且Java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)。对于交互性较强的应用而言,这种垃圾收集器是不能够接受的,一般在JavaWeb应用中是不会采用该收集器的。在IDEA中可以配置当前项目堆内存大小,如指定年轻代和老年代都使用串行垃圾收集器,并且打印垃圾回收的详细信息(堆的初始和最大内存都设置为16M):
GC日志信息解读:
DefNew:表示使用的是串行垃圾收集器。
4416K->512K(4928K):年轻代GC前,占有4416K内存,GC后,占有512K内存,总大小4928K。
0.0046102 secs:表示,GC所用的时间,单位为毫秒。
4416K->1973K(15872K):表示,GC前,堆内存占有4416K,GC后,占有1973K,总大小为15872K。
Full GC:表示,内存空间全部进行GC。
[GC (Allocation Failure) [DefNew: 4416K‐>512K(4928K), 0.0046102 secs] 4416K‐>1973K(15872K), 0.0046533 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 10944K‐>3107K(10944K), 0.0085637 secs] 15871K‐>3107K(15872K), [Metaspace: 3496K‐>3496K(1056768K)], 0.0085974 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
并行垃圾回收器
并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间。这里是指,并行能力较强的机器,当然了,并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是一样的,只是并行执行,速度更快些,暂停的时间更短一些。如ParNewGC:
# 参数
‐XX:+UseParNewGC ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
# 打印出的信息
[GC (Allocation Failure) [ParNew: 4416K‐>512K(4928K), 0.0032106 secs] 4416K‐>1988K(15872K), 0.0032697 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
ParallelGC收集器工作机制和ParNewGC收集器一样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。如:
-XX:+UseParallelGC:年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。
-XX:+UseParallelOldGC:年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
-XX:MaxGCPauseMillis:设置最大的垃圾收集时的停顿时间,单位为毫秒。需要注意的是ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。该参数使用需谨慎。
-XX:GCTimeRatio:设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)。 它的值为0~100之间的数字,默认值为99,也就是垃圾回收时间不能超过1%。
-XX:UseAdaptiveSizePolicy:自适应GC模式,垃圾回收器将自动调整年轻代、老年代等参数,达到吞吐量、 堆大小、停顿时间之间的平衡。一般用于手动调整参数比较困难的场景,让收集器自动进行调整。
# 参数
‐XX:+UseParallelGC ‐XX:+UseParallelOldGC ‐XX:MaxGCPauseMillis=100 ‐XX:+PrintGCDetails ‐Xms16m ‐Xmx16m
# 打印的信息
[GC (Allocation Failure) [PSYoungGen: 4096K‐>480K(4608K)] 4096K‐ >1840K(15872K), 0.0034307 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 505K‐>0K(4608K)] [ParOldGen: 10332K‐ >10751K(11264K)] 10837K‐>10751K(15872K), [Metaspace: 3491K‐ >3491K(1056768K)], 0.0793622 secs] [Times: user=0.13 sys=0.00, real=0.08 secs]
CMS垃圾回收器
CMS全称Concurrent Mark Sweep,是一款并发的、使用标记清除算法的垃圾回收器,该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。CMS垃圾回收器的执行过程如下:
1)初始化标记(CMS-initial-mark),标记root,会导致stw。
2)并发标记(CMS-concurrent-mark),与用户线程同时运行。
3)预清理(CMS-concurrent-preclean),与用户线程同时运行。
4)重新标记(CMS-remark),会导致stw。
5)并发清除(CMS-concurrent-sweep),与用户线程同时运行。
6)调整堆大小,设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片。
7)并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行。
G1垃圾回收器(重点、默认)
G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优: 开启G1垃圾收集器、设置堆的最大内存、设置最大的停顿时间。G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC和Full GC,在不同的条件 下被触发。G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。
在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片问题的存在了。在G1中,有一种特殊的区域,叫Humongous区域。如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
Young GC
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。每个区(Region)初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是xx Region的xx Card。
Mixed GC
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region,这里需要注意的是,是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。MixedGC触发由参数-XX:InitiatingHeapOccupancyPercent=n决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。
G1回收器相关参数
使用G1垃圾收集器:-XX:+UseG1GC
设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200毫秒:-XX:MaxGCPauseMillis
设置的G1区域的大小。值是2的幂,范围是1MB到32MB之间。目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000:-XX:G1HeapRegionSize=n
设置STW工作线程数的值。将n的值设置为逻辑处理器的数量。n的值与逻辑处理器的数量相同,最多为8:-XX:ParallelGCThreads=n
设置并行标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右:-XX:ConcGCThreads=n
设置触发标记周期的Java堆占用率阈值。默认占用率是整个Java堆的45%:-XX:InitiatingHeapOccupancyPercent=n
在线GC日志分析工具
GC Easy是一款在线的可视化工具,易用、功能强大,官方地址:https://gceasy.io/
‐XX:+PrintGC:输出GC日志
‐XX:+PrintGCDetails:输出GC的详细日志
‐XX:+PrintGCTimeStamps:输出GC的时间戳(以基准时间的形式) ‐XX:+PrintGCDateStamps:输出GC的时间戳(以日期的形式,如 2013‐05-04T21:53:59.234+0800)
‐XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息
‐Xloggc:../logs/gc.log:日志文件的输出路径
‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=100 ‐Xmx256m ‐XX:+PrintGCDetails ‐ XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐XX:+PrintHeapAtGC ‐ Xloggc:F://test//gc.log
导出GC日志,通过GC Easy打开即可进行分析:
内存分配机制
对象的内存分配通常是在Java堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的Eden区,如果启动了本地线程缓冲,将按照线程优先在TLAB上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种普遍规则:
1)对象优先在Eden区分配:多数情况,对象都在新生代Eden区分配。当Eden区分配没有足够的空间进行分配时,虚拟机将会发起一次Minor GC。如果本次GC后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。这里我们提到Minor GC,如果你仔细观察过GC日常,通常我们还能从日志中发现Major GC/Full GC。Minor GC是指发生在新生代的 GC,因为Java对象大多都是朝生夕死,所有Minor GC非常频繁,一般回收速度也非常快;Major GC/Full GC是指发生在老年代的GC,出现了Major GC通常会伴随至少一次Minor GC。Major GC的速度通常会比Minor GC慢10倍以上。
2)大对象直接进入老年代:所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发GC以获取足够的连续空间来安置新对象。新生代使用的是标记清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致Eden区和两个Survivor区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。
3)长期存活对象将进入老年代:虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在Eden区出生,并且能够被Survivor容纳,将被移动到Survivor空间中,这时设置对象年龄为1。对象在Survivor区中每熬过一次Minor GC,年龄就加1,当年龄达到一定程度(默认15)就会被晋升到老年代。
JVM调优
调优案例
当程序卡顿、请求吞吐量(QPS)变慢、stop the word(STW)停顿时间过长、内存溢出(OOM)时,如误写死循环或本地开发内存不足,这时我们首先导出JVM内存使用情况日志进行分析(可以使用mat工具进行分析),主要分析堆内存的使用情况。然后我们输出GC日志,通过在线工具GC Easy或jconsole、jvisualvm分析吞吐量、停顿时间和垃圾回收频率,最后我们通过分析的情况调整堆内存的大小和分配比例,不断调式使GC的吞吐量和停顿时间到达稳定高速的状态。
优质文章推荐:
面试回答:https://juejin.cn/post/7034669867286396958
调优流程:https://juejin.cn/post/6844903506093015053
堆调优参数
# 常用参数
-Xms:初始堆大小,JVM启动的时候,给定堆空间大小。
-Xmx:最大堆大小,JVM运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
-XX:NewSize=n 设置年轻代初始化大小大小
-XX:MaxNewSize=n 设置年轻代最大值
-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代+年老代和的1/4
-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10,比值默认就为8
-Xss:设置每个线程的堆栈大小。JDK5后每个线程Java栈大小为1M,以前每个线程堆栈大小为 256K。
-XX:ThreadStackSize=n 线程堆栈大小
-XX:PermSize=n 设置持久代初始值
-XX:MaxPermSize=n 设置持久代大小
-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。
# 不常用参数
-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
设置参数的方式
1)可以在IDEA,Eclipse,JVM工具里设置
2)如果上线了是WAR包的话可以在Tomcat设置
3)如果是Jar包直接执行命令进行设置,如:
java -Xms1024m -Xmx1024m -jar springboot_app.jar
GC调优参数
-XX:+UseSerialGC 设置串行收集器,年轻带收集器。
-XX:+UseParNewGC 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
-XX:+UseParallelGC 设置并行收集器,目标是目标是达到可控制的吞吐量。
-XX:+UseParallelOldGC 设置并行年老代收集器,JDK6.0支持对年老代并行收集。
-XX:+UseConcMarkSweepGC 设置年老代并发收集器。
-XX:+UseG1GC 设置G1收集器,JDK1.9默认垃圾收集器
-XX:MaxGCPauseMillis 设置目标停顿时间
标签:对象,XX,线程,内存,JVM,GC
From: https://www.cnblogs.com/xdzy/p/16941758.html