JVM/GC
- JVM(java虚拟机)
-
- MAT
- jstack(将正在运行的JVM的线程进行快照并且打印出来)
- 死锁
- VisualVM工具(监控线程内存使用情况)
- JMX
- 分析堆日志
- 什么情况下可能需要JVM调优
- 补充JVM内部结构
- [JVM 调优策略(补充)](#JVM 调优策略(补充))
- GC
-
- 垃圾回收算法
- 收集器
- 3.G1垃圾收集器(重点)jdk1.7开始1.9默认的回收器
-
- [Young GC模式](#Young GC模式)
- [Mixed GC](#Mixed GC)
- 可视化的GC日志分析工具
已更新完---有需要补充的请留言,大家一起学习
JVM(java虚拟机)
1.在生产环境中出现应用卡主的现象,日志不输出,程序没有反应
2.cpu占用过高
3.在多线程应用下,如何分配线程的数量
4.死锁
5.内存泄漏
等等
参数类型 | 实例 | 解释说明 |
---|---|---|
标准参数 | -help | 很稳定,占时的JVM版本中都不会进行改变 |
-X参数(非标准参数) | -Xint | 不稳定,在未来的一些版本中可能会进行一些改变 |
-XX参数(使用率较高) | -XX:UseSerialGC | 再JVM的调优或者JVM的debugger中使用 |
java
//-showversion 参数表示先打印版本信息,再去执行后面的命令,再调试的时候非常有用
情况\参数 | -server | -clinet |
---|---|---|
初始堆空间 | 大一些 | 小一些 |
默认回收器 | 并行垃圾回收器 | 串行垃圾回收器 |
启动速度 | 慢 | 快 |
运行速度 | 快 | 慢 |
32位操作系统(window) | - | 默认 |
32位操作系统(其他)2GB以上的内存,2个以上的CPU | 默认 | 否则选择该模式 |
64位操作系统 | 是 | 不支持 |
java
// 进入根目录创建文件
mkdir /test
cd /test/
ll
vim TestJVM.java
//将刚刚的代码复制进去
public static void main(String[] args) {
String str = System.getProperty("str");
if (str == null) {
System.out.println("this str =null");
} else {
System.out.println(str);
}
}
//然后编译
javac TestJVM.java
//运行文件
java TestJVM
//设置里面的参数进行运行
java -Dstr=hello TestJVM
//结果如下
java
//按照刚刚的方式使用showversion,打印出来的就是显示版本号的基本进信息然后就是this str =null的信息
java -client -showversion TestJVM
java -server-showversion TestJVM
java -X
-X参数 | 作用 | 解释说明 |
---|---|---|
-Xmixed | 混合模式执行 (默认) | 价格解释模式和编译迷失进行混合使用,由JVM自己决定,这是jvm的默认模式也是推荐模式 |
-Xint | 仅解释模式执行 | 标记会强制JVM执行所有的字节代码,这样就会降低了运行速度,通常是低10倍以上 |
-Xcomp | 编译模式 | 和Xint相反,jvm再第一次使用的时候会把所有的字节码编译到本地代码,从而大大的速度上的优化,但是很多应用在使用这个模式的时候都会有一些性能的损失.但是比使用-Xint损失少点,原因是他没有让JIT启动it编译器的全部功能,JIT编译器可以Udine是否需要编译做出判断,如果所有的代码都进行了编译,对于一些只执行一次代码就没有意义了 |
-Xbootclasspath | <用 ; 分隔的目录和 zip/jar 文件>设置搜索路径以引导类和资源 | -- |
-Xbootclasspath/a | <用 ; 分隔的目录和 zip/jar 文件> 附加在引导类路径末尾 | -- |
-Xbootclasspath/p | <用 ; 分隔的目录和 zip/jar 文件>置于引导类路径之前 | -- |
-Xdiag | 显示附加诊断消息 | -- |
-Xnoclassgc | 禁用类垃圾收集 | -- |
-Xincgc | 启用增量垃圾收集 | -- |
-Xloggc:< file > | 将 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 | 显示所有与区域设置相关的设置并继续 | -- |
-XX参数
类型 | 格式 | 实例 |
---|---|---|
boolean类型 | -XX:[+ -]< name >表示启用或者禁用< name >属性 | -XX:DisableExplicitGC表示禁用手动调用GC操作,也就是说System.gc()无效 |
非boolean类型 | -XX:< name > =< value >表示< name >属性值为< value > | -XX:NewRatio = 1表示新生代和老年代的比值 |
下面的属于-XX的参数范围
参数 | 解释说明 |
---|---|
-Xms | 设置JVM的堆内存初始大小 |
-Xmx | 设置JVM的堆内存最大大小 |
java
//比如运行下面的程序实例
java -Xms512m -Xmx2048m TestJVM来
什么时候需要查看JVM的运行参数
1.运行java命令的时候打印出运行参数
运行java命令时候打印参数需要添加-XX:PrintFlagsFinal参数即可
实例:java -XX:PrintFlagsFinal TestJVM
上面图的等于说明是默认值,如果显示的是:=说明是被修改之后的值如果现在我想要将ZerroTLAB修改为true只需要这样就可以了
java -XX:PrintFlagsFinal -XX:+ZerroTLAB TestJVM
2.查看正在运行的java进程的参数
如果需要查看正在运行的jvm就需要借助月jinfo命令来进行查看
首先,启动一个tomcat用于测试来观察运行的jvm参数
cd /tmp/
rz 上传
tar -xvf apache-tomcat-7.0.57.tar.gz
cd apache-tomcat-7.0.57
cd bin/
./startup.sh
访问路径为:http://localhost:8080/
使用jps查看进程
使用jps -l 查看进程的全包名
查看所有的参数,用法:jinfo -flags < 进程id >
jinfo -flags 5269
jvm的内存模型
1.7版本
1.8版本
可以看出1.8版本的组成为:年轻代 + 老年代
年轻代: Eden + 2个Survivor
老年代:OldGen
元数据空间:所占用的内存空间不是再虚拟机内部的,而是再本地内存空间中
通过jstat命令进行查看堆内存的使用情况
jstat命令可以查看堆内存各个部分的使用量,以及加载类的数量格式如下:
jstat [-命令选项] [vmid][间隔时间/毫秒][查询次数]
查看class加载统计
jps
7080 jps
14440
jstat -class 14440
参数 | 解释说明 |
---|---|
Loaded | 加载class的数量 |
Bytes | 所占用的空间大小 |
Unloaded | 未加载数量 |
Bytes | 未加载占用的空间 |
Time | 总时间 |
查看编译统计
jstat -compilr 14440
参数 | 解释说明 |
---|---|
Compiled | 编译的数量 |
Failed | 失败数量 |
Invalid | 不可用数量 |
Time | 总时间 |
FailedType | 失败类型 |
FailedMethod | 失败的方法 |
垃圾回收统计
jstat -gc 14440
这里指的是每一秒打印一次一共打印5次
jstat -gc 14440 1000 5
参数 | 解释说明 |
---|---|
S0C | 第一个Survivor区的大小 |
S1C | 第二个Survivor区的大小 |
S0U | 第一个Survivor区的使用大小 |
S1U | 第二个Survivor区的使用大小 |
EU | Eden区的大小 |
EC | Eden区的使用大小 |
OC | Old区的大小 |
OU | Old区的使用大小 |
MC | 方法区大小 |
MU | 方法区使用大小 |
CCSC | 压缩类空间大小 |
CCSU | 压缩类空间使用大小 |
YGC | 年轻代垃圾回收次数 |
YGCT | 年轻代垃圾回收耗时时间 |
FGC | 老年代垃圾回收次数 |
FGCT | 老年代垃圾回收耗时时间 |
GCT | 垃圾回收耗时总时长 |
jmap的使用
查看内存的使用情况
jmap -heap 27472
查看内存中对象的数量以及大小
查看所有的对象,包括活跃的和非活跃的对象
jmap -histo < pid > | more
查看活跃的对象jmap -histo:live < pid > | more
例如:jmap -histo:live: 27472 | more
对象 | 说明 |
---|---|
B | BYTE |
C | CHAR |
D | DOUBLE |
F | FLOAT |
I | INT |
J | LONG |
Z | BOOLEAN |
[ | 数组例如:[I表示的就是int[] |
[L+类名 | 其他对象 |
MAT
将内存使用的情况dump到文件中
有时候需要将JVM当前的内存情况dump(快照)到文件中,然后针对这个文件进行分析,jmap的用法
jmap -dump:format=b,file=dumpFileName < pid >
例如:jmap -dump:format=b,file=H:\ dump.dat 27472
再使用jvm的导入到MAT工具中进行分析需要用到的参数是--当我们发生内存溢出的时候,将我们的内存使用情况进行一个快照(设置内存的初始内存大小,设置内存的最大内存大小)
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
生成的文件名就是上面图片提到的java_pid7600.hprof文件
使用MAT工具对dump文件进行分析
打开文件
选择之前的dump的文件
jstack(将正在运行的JVM的线程进行快照并且打印出来)
用法:
jstack < pid >
这里就是输出线程的执行情况
下面是线程的状态图
状态 | 解释说明 |
---|---|
初始状态(NEW) | 创建一个Thread对象,但是还没有调用start()启动线程时,线程处于初始状态 |
运行状态(Runnable) | 再java中,运行状态包括就绪状态和运行状态 |
运行状态(就绪状态) | 这个状态下的线程已经获取到了所要执行的资源,只需要分配CPU执行权即可执行,所有的就绪状态的线程都是存放在就绪队列中的 |
运行状态(运行状态) | 获取到CPU的执行权,正在执行线程,由于一个cpu同一时刻只能执行一个线程,所以每一个cpu每个时刻只有一条运行状态的线程 |
阻塞状态(Blocked) | 当每一个正在执行的线程请求某一条资源失败的时候就会进入阻塞状态,在java中阻塞状态专指请求锁失败时进入的状态,由于一个阻塞队列存放所有的阻塞状态的线程,处于阻塞状态的线程会不断的请求资源,一旦请求成功,就会进入就绪状态等待执行 |
等待状态(Waiting) | 当前线程中调用wait,join,park函数的时候,当前线程就会进入等待状态,也有一个等待的队列存放在所有的等待状态的线程;线程处于等待状态表示需要等待其他的线程的指示才能继续运行;进入等待状态的线程会释放CPU的执行权,并且释放资源(如:锁) |
超时等待状态(Timed_Waiting) | -当运行的线程调用sleep(time),wait,join,parkNanos,parkUntil的时候,就会进入到这个状态; 他和等待状态一样,并不是因为请求不到资源,而是主动的进入,并且进入后需要其他线程的唤醒;进入这个状态后释放CPU执行权和占有的资源; 与等待状态的区别就是,到了超时时间以后自动进入阻塞队列,开始竞争锁 |
终止状态(Terminated) | 线程执行结束后的状态 |
死锁
部署的应用程序没有任何反应了,中间也没有任何的输出
执行当前的代码然后通过jps获取当前的进程之后使用jstack < pid > 来获取当前的代码的执行情况
这样就发现是出现了死锁的情况
VisualVM工具(监控线程内存使用情况)
这个工具是可以监控线程以及内存的使用情况,查看方法的CPU的时间和内存中的对象,已经被GC的对象,反向查看分配的堆栈(比如现在有100个String对象分别是有哪几个兑现分配出来的)
VisualVM命令
- 内存信息
- 线程信息
- Dump堆(本地进程)
- Dump线程(本地进程)
- 打开对Dump,堆Dump可以用jmap来生成
- 打开线程Dump
- 生成应用快照(包含内存信息,线程信息等等)
- 性能分析,CPU分析(各个方法调用时间,检查那些方法耗时长),内存分析(各类对象占用的内存,检查那些类占用的内存多)
监控远程的JVM
JMX
是一个应用程序,设备,系统等植入管理功能的框架,JMX可以跨一系列的异构操作系统平台,系统体系和网络传输协议,灵活的开发无缝集成的系统,网络和服务管理应用
远程监控tomcat
再远程的tomcat添加一些参数
再tomcat的bin目录下修改catalina.sh添加如下的参数
保存退出
分析堆日志
java
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f8e04001800 nid=0x5103 waiting on condition [0x000070000e2ef000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.example.MyApp.processData(MyApp.java:45)
at com.example.MyApp.run(MyApp.java:23)
at java.lang.Thread.run(Thread.java:748)
"Thread-2" #13 prio=5 os_prio=0 tid=0x00007f8e04002000 nid=0x5203 waiting on condition [0x000070000e3f2000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.example.MyApp.processData(MyApp.java:45)
at com.example.MyApp.run(MyApp.java:23)
at java.lang.Thread.run(Thread.java:748)
"Thread-3" #14 prio=5 os_prio=0 tid=0x00007f8e04002800 nid=0x5303 waiting on condition [0x000070000e4f5000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076af8aeb8> (a java.util.concurrent.CountDownLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:
997)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at com.example.MyApp.processData(MyApp.java:60)
at com.example.MyApp.run(MyApp.java:23)
at java.lang.Thread.run(Thread.java:748)
从上述得到的信息有
1.有多个线程在运行,每个线程都有一个唯一的线程ID(tid)和线程名称。
2.线程的状态(Thread.State)反映了线程当前的活动状态,如等待、休眠、阻塞等。
3.每个线程的调用栈显示了从线程入口点开始到当前执行位置的方法调用链。
分析日志
1.线程等待和休眠:线程处于等待和休眠状态,可能是因为某些操作需要等待外部资源的响应或者进行了不必要的休眠。通过检查这些操作并尝试减少等待时间或优化休眠逻辑。
2.大量线程执行相同的方法:多个线程都在执行相同的com.example.MyApp.processData方法。这可能意味着该方法存在性能瓶颈或重复计算的情况。可以分析该方法并尝试优化算法或使用并发技术来提高处理速度。
3.线程阻塞:示例中的"Thread-3"线程被阻塞在java.util.concurrent.CountDownLatch.await方法上。这可能是因为某个条件没有被满足而导致线程无法继续执行。检查并确保条件的正确设置和处理,以避免线程的长时间阻塞。
什么情况下可能需要JVM调优
- 堆内存持续增长:如果应用程序的堆内存持续增长并且接近或达到了最大内存限制(由 -Xmx 参数设置),这可能表明存在内存泄漏或者内存使用不合理的情况,需要进行调优来优化内存使用。
- 频繁的Full GC:如果应用程序中频繁发生Full GC,即对整个堆进行回收的情况,这可能会导致较长的停顿时间和性能下降。调优的目标是尽量减少Full GC的次数。
- 垃圾回收停顿时间过长:如果垃圾回收的停顿时间超过了可接受的范围(一般认为超过1秒),可能会影响应用程序的响应性能和用户体验。调优的目标是减小垃圾回收的停顿时间。
- 内存异常:如果应用程序经常遇到内存异常,如OutOfMemoryError,表明应用程序的内存使用超出了JVM的限制,需要调优来提高内存的利用率和稳定性。
- 大量占用内存的本地缓存:如果应用程序中使用了大量的本地缓存,并且占用了大量的内存空间,可能会导致内存不足的问题。调优的目标是优化缓存策略和内存管理,减少内存占用。
- 性能不佳或不稳定:如果应用程序的吞吐量和响应性能不高或不稳定,可能是由于内存管理不当导致的。调优的目标是提高应用程序的性能和稳定性。
- 除了命令行参数外,可以在应用程序代码使用System.setProperty()方法来动态设置JVM参数如:
java
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");
补充JVM内部结构
1.类加载器(Class Loader)
类加载器负责将Java字节码文件加载到内存中,并将其转换为可执行的类。JVM使用了三个主要的类加载器:启动类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)和应用程序类加载器(Application Class Loader)
2.运行时数据区(Runtime Data Area)
方法区(Method Area):用于存储类的结构信息、常量、静态变量等。
堆(Heap):用于存储对象实例,包括年轻代和年老代。
虚拟机栈(VM Stack):每个线程在运行时都会创建一个虚拟机栈,用于存储局部变量、方法参数、方法调用和返回等信息。
本地方法栈(Native Method Stack):用于执行本地方法的栈。
程序计数器(Program Counter):记录线程当前执行的字节码指令地址。
3.垃圾收集器(Garbage Collector)
垃圾收集器负责自动管理内存,回收不再使用的对象。JVM中有多种垃圾收集器可供选择,如Serial、Parallel、CMS(Concurrent Mark and Sweep)和G1(Garbage First)等。它们采用不同的算法和策略来进行垃圾回收。
4.即时编译器(Just-In-Time Compiler,JIT)
即时编译器将Java字节码转换为本地机器代码,以提高程序的执行效率。JIT根据代码的热点(HotSpot)进行动态编译,将频繁执行的代码优化为本地机器代码。
5.安全管理器(Security Manager)
安全管理器用于保护JVM和应用程序免受恶意代码的攻击。它负责检查和控制Java程序的访问权限,确保程序运行在安全的环境中。
6.JNI(Java Native Interface)
JNI允许Java程序调用本地方法,与底层系统交互。通过JNI,Java程序可以访问操作系统的功能和其他编程语言的库。
JVM 调优策略(补充)
1.内存管理和垃圾回收优化
基于实时数据分析的垃圾回收:通过实时数据分析,优化垃圾回收算法的行为,减少停顿时间和内存开销。
分代垃圾回收优化:针对不同对象的生命周期,采用不同的垃圾回收策略,例如针对年轻代和老年代的不同处理方式。
压缩指针:使用压缩指针技术,减少对象引用所占的内存空间,从而增加可用内存量。
2.JIT编译器优化
激进编译:通过激进编译技术,将更多的代码段编译成本地代码,提前优化关键路径,减少解释执行的开销。
编译优化反馈循环:通过收集运行时数据,优化编译器的决策过程,更好地适应应用程序的行为模式。
混合模式执行:结合解释执行和即时编译执行,根据代码的特征和执行频率,选择最优的执行方式。
3.并发性优化
并发垃圾回收:通过并行和并发的方式执行垃圾回收任务,充分利用多核处理器的优势,减少垃圾回收的停顿时间。
无锁数据结构:采用无锁或低锁的数据结构,减少线程之间的竞争和阻塞,提高并发性能。
并行算法和数据结构:设计并行算法和数据结构,充分利用多核处理器的并行计算能力,提高应用程序的吞吐量和响应性能。
4.使用工具进行分析和优化
VisualVM:提供实时监控和分析JVM的性能和内存使用情况,帮助识别性能瓶颈和内存泄漏问题。
Mission Control:提供高级的分析功能,帮助深入了解应用程序的性能特征,并进行实时调优。
Java Flight Recorder:记录应用程序的运行数据,可进行离线分析和调优。
GC
垃圾回收:程序运行的时候必然需要申请内存的资源,无效的对象资源如果不及时进行处理的就会一直占用内存资源,最终导致内存溢出
java语言中有自动的垃圾回收机制就是GC
垃圾回收算法
1.引用计数法
原理:假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败的时候,对象A的引用计数器就-1,如果对象A的计数器值为0,就是说明A没有被引用,可以被回收了
优点:
1.实用性较高,无需等到内存不够的时候,才可以进行回收,运行的时候根据对象的计数器是否为0,就可以直接进行回收了
2.在垃圾回收的过程中,引用无需挂起,如果申请内存的时候内存不足,则会立即outofmenber错误
3.区域性,更新对象的计数器的时候,只是影响到该对象,不会扫描全部的对象
缺点:
1.对象每次被引用的时候,都需要去更新计数器,有一定时间的开销
2.浪费cpu资源,即使是内存足够的情况下,任然运行时进行着计数器的统计
3.无法解决循环引用的问题(最大的缺点)
java
循环引用
A a = new A();
B b = new B();
a.b=b;
b.a=a;
a=null;
b=null;
2.标记清除发
是将垃圾护手分为2个阶段,分别为标记和清除
1.标记:从根节点开始标记引用的对象
2.清除:未被标记引用的对象就是垃圾回收对象,可以被清理
暂停程序线程,没有被标记的对象会被回收清除掉然后被标记的对象留下来并进行重置变为未标记的状态,恢复程序线程,程序继续运行
优点:
1.解决了循环引用的问题
缺点:
1.效率比较低,标记和清除2个动作都是需要遍历所有的对象,并且再GC的时候需要暂停应用程序,对于交互性要求高的应用而言这个体验是非常差的
2.通过标记清除的算法清理出来的内存,碎片化比较严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来内存是不连贯的
3.标记压缩算法
再标记清除算法的基础之上做的优化,和标记清除算法一样,也是从根节点开始,对对象的应用进行标记,在清理的阶段,并不是简单地清理未标记的对象,而是将存货的对象压缩到哦内存的一段,然后清理边界意外的垃圾,从而解决碎片化的问题
优点:解决了标记清除法里面而定碎片化问题
缺点:标记压缩算法多了一个压缩的步骤,这样就会导致其中的清除的整体效率受到了影响
4.复制算法
复制算法的核心是:将原有的内存空间一分为二,每次只用到其中的一块,在垃圾回收的时候,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换内存的角色,完成垃圾回收
如果内存的垃圾对象比较多的情况下需要复制的对象比较少,这种情况下是和使用这个方式并且效率比较高反之不适用
优点:
1.在垃圾对象多的情况下,效率高
2.清理以后,内存无碎片
缺点:
1.再垃圾对象少的时候不适合
2.分配的2块内存空间,再同一时刻只能使用一半,内存使用率较低
5.分代算法
分代算法指的是根据回收对象的特点进行选择,再jvm中,年轻代适合复制算法,老年代适合标记清除或者标记压缩算法
算法 | 优点 | 缺点 | 说明 |
---|---|---|---|
引用计数法 | 1.实用性较高,无需等到内存不够的时候,才可以进行回收,运行的时候根据对象的计数器是否为0,就可以直接进行回收了 2.在垃圾回收的过程中,引用无需挂起,如果申请内存的时候内存不足,则会立即outofmenber错误 3.区域性,更新对象的计数器的时候,只是影响到该对象,不会扫描全部的对象 | 1.对象每次被引用的时候,都需要去更新计数器,有一定时间的开销2.浪费cpu资源,即使是内存足够的情况下,任然运行时进行着计数器的统计 3.无法解决循环引用的问题(最大的缺点) | 假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败的时候,对象A的引用计数器就-1,如果对象A的计数器值为0,就是说明A没有被引用,可以被回收了 |
标记清除算法 | 1.解决了循环引用的问题 | 1.效率比较低,标记和清除2个动作都是需要遍历所有的对象,并且再GC的时候需要暂停应用程序,对于交互性要求高的应用而言这个体验是非常差的 2.通过标记清除的算法清理出来的内存,碎片化比较严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来内存是不连贯的 | 是将垃圾护手分为2个阶段,分别为标记和清除1.标记:从根节点开始标记引用的对象2.清除:未被标记引用的对象就是垃圾回收对象,可以被清理;暂停程序线程,没有被标记的对象会被回收清除掉然后被标记的对象留下来并进行重置变为未标记的状态,恢复程序线程,程序继续运行 |
标记压缩算法 | 解决了标记清除法里面而定碎片化问题 | 标记压缩算法多了一个压缩的步骤,这样就会导致其中的清除的整体效率受到了影响 | 再标记清除算法的基础之上做的优化,和标记清除算法一样,也是从根节点开始,对对象的应用进行标记,在清理的阶段,并不是简单地清理未标记的对象,而是将存货的对象压缩到哦内存的一段,然后清理边界意外的垃圾,从而解决碎片化的问题 |
复制算法 | 1.在垃圾对象多的情况下,效率高 2.清理以后,内存无碎片 | 1.再垃圾对象少的时候不适合 2.分配的2块内存空间,再同一时刻只能使用一半,内存使用率较低 | 将原有的内存空间一分为二,每次只用到其中的一块,在垃圾回收的时候,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空,交换内存的角色,完成垃圾回收 |
分代算法 | -- | -- | 分代算法指的是根据回收对象的特点进行选择,再jvm中,年轻代适合复制算法,老年代适合标记清除或者标记压缩算法 |
收集器
参数 | 说明 |
---|---|
-XX:UseSerialGC | 指定的年轻代和老年代都使用串行垃圾收集器 |
-XX:+PrintGCDetails | 打印垃圾回收的详细信息 |
-XX:UseParallelGC | 年轻代使用ParallelGC垃圾回收期,老年代使用串行回收器 |
-XX:UseParallelOldGC | 年轻代使用ParallelGC垃圾回收期,老年代使用ParallelOld垃圾回收器 |
-XX:MaxGCPauseMillis | (设置最大的垃圾收集时候的停顿时间,单位毫秒,需要注意的是ParallelGC为了达到设置的停顿时间,可能会调整堆的大小或者其他的参数,如果堆的大小设置的比较小,就会导致GC工作变得很频繁,反而可能会影响到性能,这个参数使用的时候需要谨慎处理) |
-XX:GCTimeRatio | (设置垃圾回收时间占程序运行时间的百分比,公式:1/(1+n),他的值为0~100之间的数字,默认值为99,也就是垃圾回收事假不能超过1%) |
-XX:UseAdaptiveSizePolicy | (自适应GC模式,垃圾回收器将会自动的调整新生代,老年代等参数,达到吞吐量,堆大小,停顿时间之间的平衡;一般用于手动的调整比较困难的场景,让收集器自动的进行调整) |
-XX:+UseG1GC | 使用G1垃圾收集器 |
-XX:+MaxGCPauseMillis | 设置期望达到的最大GC停顿时间指标,默认是200毫秒,但是jvm不一定能保证达到这个指标 |
-XX:+ParallelGCthreads=n | 设置STW工作线程数的值,将n的值设置为逻辑处理器的值.n的值与逻辑处理器的数量相同,最多为8 |
-XX:+ConcGCThreads=n | 设置并行标记的线程数,将n设置为并行垃圾回收线程数的1/4左右 |
-XX:+G1HeapRegionSize | 设置的G1区域的大小,值是2的幂,范围是1mb~32mb之间,目标是根据最小dejava堆大小划分为≈2048个区域, 默认是堆内存的1/2000 |
-XX:+InitiatingHeapOccupancyPercent=n | 设置处罚标记周期的java堆占用率阀值,默认占用率是整个java堆的45% |
参数 | 解读 | 冒号后面的内容解释 |
---|---|---|
DefNew | 表示使用的是串行垃圾收集器 | 4416K->512K(4928K)====>表示年轻代GC占用的内存是4416K内存,GC之后占用512K内存总大小4928K;0.0046102 secs =====>表示的是GC所用的时间单位毫秒 ; |
-XX:+PrintGCDetails | 打印垃圾回收的详细信息 | -- |
1.串行垃圾收集器
指的是单线程进行的垃圾回收,垃圾回收的时候只有一个线程在进行工作,并且java应用中的所有的线程都需要进行暂停,等待垃圾回收的完成这个现象叫做STW-----这个应用的场景特别少
在程序运行的过程中添加2个参数即可
1.-XX:+UseSerialGC(指定的年轻代和老年代都使用串行垃圾收集器)
2.-XX:+PrintGCDetails(打印垃圾回收的详细信息)
java
//设置堆的初始和最大内存值为16M 串行收集器
-XX:+UseSerialGC -XX:+PrintGCDetails -Xms16m -Xmx16m
2.并行垃圾收集器
并行的垃圾收集器再创航的垃圾收集器的基础上做的改进,将单线程改为多线程的垃圾回收,这样缩短回收的时间,这里的手机过程也是会暂停应用程序的
1.ParNew垃圾收集器
ParNew垃圾收集器是工作再年轻代上的,只是将串行的垃圾收集器改为并行的
通过:-XX:+UseParNewGC参数设置年轻代使用ParNew回收期,老年代使用的依然是串行收集器
2.ParallelGC垃圾收集器
ParallelGC收集器工作机制和ParNew收集器一样,只是在此基础上,新增了2个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效
相关参数:
-XX:UseParallelGC(年轻代使用ParallelGC垃圾回收期,老年代使用串行回收器)
-XX:UseParallelOldGC(年轻代使用ParallelGC垃圾回收期,老年代使用ParallelOld垃圾回收器)
-XX:MaxGCPauseMillis(设置最大的垃圾收集时候的停顿时间,单位毫秒,需要注意的是ParallelGC为了达到设置的停顿时间,可能会调整堆的大小或者其他的参数,如果堆的大小设置的比较小,就会导致GC工作变得很频繁,反而可能会影响到性能,这个参数使用的时候需要谨慎处理)
-XX:GCTimeRatio(设置垃圾回收时间占程序运行时间的百分比,公式:1/(1+n),他的值为0~100之间的数字,默认值为99,也就是垃圾回收事假不能超过1%)
-XX:UseAdaptiveSizePolicy(自适应GC模式,垃圾回收器将会自动的调整新生代,老年代等参数,达到吞吐量,堆大小,停顿时间之间的平衡;一般用于手动的调整比较困难的场景,让收集器自动的进行调整)
2.CMS垃圾收集器
是一款并发,使用标记清除算法的垃圾回收器该回收器是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置的
CMS垃圾回收器的执行过程如下
初始化标记:标记root,会导致stw(程序暂停上面有解释)
并发标记,与用户线程同时运行
预清理与用户线程同时运行
重新标记,会导致stw
并发清除,与用户线程同时运行
调整堆大小,设置CMS再清理之后进行内存压缩,目的是清理内存中的碎片
并发重置状态等待下次CMS的触发,与用户线程同时运行
3.G1垃圾收集器(重点)jdk1.7开始1.9默认的回收器
G1的设计原则就是简化jvm性能调优,三步调优
1.开启G1垃圾收集器
2.设置堆的最大内存
3.设置最大的停顿时间
三种模式Young GC ,Mixed GC 和Full GC
相比于其他的垃圾收集器,最大的区别在于他取消了年轻代,老年代的物理划分,取而代之的是将堆划分为若干个区域,这些区域中包含了有逻辑上的年轻代和老年代区域
这样做的好处就是,不需要单独的空间对每一个代进行设置,不需要担心每个代内存是否足够的问题
在G1划分的区域中,年轻代的垃圾收集器依然是采用stw的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域完成清理的工作
这就意味着在正常处理过程中,G1完成了堆的压缩,这样也就不会有cms内存碎片的问题存在了
Humongous
1.如果一个对象占用的空间超过了分区容量的50%以上,G1收集器就认为这是一个巨型对象
2.这些巨型对象,默认直接会被分配到老年代,但是如果他是一个短期存在的巨型对象,就会对垃圾收集器造成负面的影响
3.为了解决这个问题,G1划分了一个Humongous区,他是专门的存放巨型对象的,如果一个H村放不下巨型对象,那么就会寻找连续的H分区来进行存储,为了能找到连续的H区有时候就得启动Full GC
Young GC模式
主要是对Eden区进行GC,他在Eden空间耗尽时候会被触发
1.Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升为老年代空间
2.Survivor区的数据移动到新的Survivor区中,也会有部分的数据晋升为老年代空间中
3.最终Eden空间的数据为空,GC停止空间,应用线程继续执行
Mixed GC
当越来越多的对象晋升为老年代old region的时候,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,既 Mixed GC 这个算法并不是一个old GC 除了回收整个Young Region ,还会回收一部分的Old Region,这里需要注意的是:是一部分的老年代,而不是全部的老年代,可以选择那些old region进行收集,从而可以垃圾回收的耗时时间进行控制;也需要注意的是Mixed GC并不是Full GC
触发机制:参数:-XX:InitiatingHeapOccupancyPercent=n决定的,默认45%(当老年代大小占堆大小百分比达到这个阀值的时候触发)
GC2步:
1.全局并发标记
2.拷贝存活对象(evacuation)
全局并发标记5步
1.初始标记:标记从根节点直接到达对象,这个阶段会执行一次年轻化GC,会产生全局停顿
2.根区域扫描
G1 GC在初始标记的存活区扫描老年代的应用,并标记被引用的对象
该阶段与引用程序(非STW)同时运行,并且只有完成这个阶段的之后才能开始下一次STW年轻代垃圾回收
3.并发标记
G1 GC在整个堆查找可访问(存活的)对象,这个阶段与应用程序是同时运行的,可以被STW年轻代垃圾回收中断
4.重新标记
这个阶段指的是STW回收,因为程序再运行,针对上一次的标记正在修正
5.清除垃圾
清点和重置标记状态,这个阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收
拷贝存活对象
evacuation阶段是全暂停的,这个阶段把一部分的Region里面的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理
可视化的GC日志分析工具
日志打印
生成log文件
GC Easy可视化工具网址