Java GC
GC(Garbage Collection),目的是为了防止内存泄漏,更加有效的利用内存把一些长时间不用对象从内存中移除(一下内容为JDK8 HotSpot JVM)
特点
- stop-the-world
参考资料
定义垃圾
1.引用计数算法
2.可达性分析算法
引用计数算法
通过在对象头用一个值来记录被引用的次数,如果应用次数为0则被定义为垃圾可被回收。(被引用了+1,取消应用-1)
String s = new String("value");
- 引用次数+1,引用次数为 1
s = null
- 引用次数 -1,引用次数为 0
特点
- 在整个应用运行过程中,无时无刻都在记录着引用
- 如果记录两个对象相互引用,那么无法被认为是垃圾
两个对象相互引用DEMO
public class A {
public String name;
public A value;
public A(String name) {
this.name = name;
}
}
// main
A a = new A("one")
A b = new A("two")
a.value = b;
b.value = a;
a = null
b = null;
- 创建了两个对象
one
,two
且分别让变量a
,b
指向one
,two
对象 - 在让
one
,two
两个对象里面的属性相互指向,这时他们的引用次数为 2 - 移除变量
a
,b
的引用,此时one
,two
引用次数为1,但是他们因为失去了变量的引用,应用里无法再找打这两个对象。通过引用计数算法,也就无法再被回收
可达性分析算法
通过一些被称为引用链(GC Root
)的对象作为起点,从这些节点开始向下搜索,当一个对象到 GC Roots 没有任何引用链相连时,则证明该对象是不可用的。
这个方案解决了对象相互引用的问题,但是又有一个问题,哪些对象是GC Root
GC Root
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象(类信息在方法区)
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
public static void testGC(){
Sring s = new String("object");
s = null;
}
- 变量
s
为GC Root
,当变量s
为空的时候,对象object
也就断了和GC Root
的引用链
方法区中类静态属性引用的对象
public class A {
public static A value;
public String name;
public A(String name) {
this.name = name;
}
}
// main
public static void testGC(){
A s = new A("object");
s.value = new A("object2");
s = null;
}
- 变量
s
,静态属性value
都为GC Root
方法区中常量引用的对象
public class A {
public static final A value = new A("obj");
public String name;
public A(String name) {
this.name = name;
}
}
// main
public static void testGC(){
A s = new A("object");
s = null;
}
- 变量
s
,静态常量属性value
都为GC Root
回收垃圾
标记-清除算法 复制算法 标记整理算法 分代回收算法
标记 - 清除算法
- 第一步:标记哪些对象可被回收
- 第二步:回收标记了的对象,清理出内存
特点
- 有很严重的内存碎片问题
复制算法
标记-清除算法演变而来,解决了内存碎片问题
- 把内存平分为两个区域,回收时先把活着的对象放到另一个内存区域中,然后把另一个内存区域清除
特点
- 使用过程中,内存浪费有些严重,100M内存有效使用只有50M
- 总有一个区域是不被使用的
标记整理算法
- 第一步:先标记无效对象
- 第二步:有效对象向一端移动(整理)
- 第三步:清理无效对象
特点
- 解决了标记-清理内存碎片问题
- 解决了复制算法内存空间只用一半问题
- 因为引用对象内存地址频繁变更,引用对象需要重新指定新的内存,使得效率上比复制算法差很多
分代收集算法
把内存分区,不同的区域存放不同的类型对象(按时间,内存大小),不同区域使用不同的GC策略,组合使用不同的回收算法
Java 堆内存模型与回收策略
使用分代收集算法
- JVM 堆内存主要分为新生代(YoungGen)和老年代(OldGen)。新生代又在分为Eden,Survivor两个区。Survivor又分为From,To 两个区域,From,To这两个区域的内存指向每
Minor GC
一次会发生一次置换
Eden 区 当Eden没有足够的内存进行分配的时候。会触发一次Minor GC
,绝大部分对象会被清除,而一部分对象会被移动到Survivor区域中的From
内存中,之后Enden会被清空
- Minor GC 相比 Major GC 更频繁,回收速度也更快
- 如果对象内存太大会直接进入Old,为了减少Survivor复制的开销
Survivor Survivor承担着Eden与Old一层缓冲的作用,目的是让只有经历多次Minor GC
的对象才会被送到Old区域,而不是只经历了一次就被送到了Old Gen
- 为什么Survivor要分为From,To? 为了解决内存碎片化,每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。
OId Gen 老年代占据着2/3的堆内存空间,只有在 Major GC
的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。并且内存越大,STW 的时间也越长。
在内存担保机制下,以下几种情况也会进入老年代。 1、大对象 需要大量连续内存空间的对象,为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。
2、长期存活对象 虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。
3、动态对象年龄 如果 Survivor 空间中相同年龄所有对象大小的综合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。
HotSpot JVM 关于GC的配置参数
GC 种类
GC启用参数 | 年轻代算法 | 老年代算法 |
---|---|---|
-XX:+UseSerialGC | Serial (DefNew) | Serial Old(PSOldGen) |
-XX:+UseParallelGC | Parallel Scavenge (PSYoungGen) | Serial Old(PSOldGen) |
-XX:+UseParallelOldGC | Parallel Scavenge (PSYoungGen) | Parallel Old (ParOldGen) |
-XX:-UseParNewGC | ParNew (ParNew) | Serial Old(PSOldGen) |
-XX:+UseConcMarkSweepGC | ParNew (ParNew) | CMS+Serial Old(PSOldGen) |
-XX:+UseG1GC | G1 | G1 |
查看当前项目使用的GC类型
- 查询java进程
jps
- 查询是否开启某一个GC
jinfo -flag UseSerialGC java进程
jinfo -flag可以查询控制布尔变量,是开启还是关闭
- 运行过程重开启GC日志
jinfo -flag +PrintGC java 进程
GC日志详情参数
参数 | 含义 |
---|---|
-XX:+PrintGC | 打印GC日志,和 -verbose:gc 是相同的命令 |
-XX:+PrintGCDetails | 打印GC的详细日志 |
-XX:+PrintGCTimeStamps | 打印GC的时间戳(JVM启动到GC发生所经历的时间) |
-XX:+PrintGCDateStamps | 打印GC的日期时间(如:2019-05-06T19:34:52.072+0800) |
-XX:+PrintHeapAtGC | 打印GC前后的详细的堆信息 |
-XX:+PrintGCCause | 原因 |
GC日志输出
参数 | 含义 |
---|---|
-Xloggc:logs/gc.log.date +%Y-%m-%d | GC日志输出到指定文件 |
-XX:+UseGCLogFileRotation | 开启滚动GC日志 |
-XX:GCLogFileSize=512m | GC日志文件最大值 |
-XX:NumberOfGCLogFiles=5 | GC日志文件的个数 |
2014-07-18T16:02:17.606+0800(当前时间戳): 611.633(时间戳): [GC(表示Young GC) 611.633: [DefNew(单线程Serial年轻代GC): 843458K(年轻代垃圾回收前的大小)->2K(年轻代回收后的大小)(948864K(年轻代总大小)), 0.0059180 secs(本次回收的时间)] 2186589K(整个堆回收前的大小)->1343132K(整个堆回收后的大小)(3057292K(堆总大小)), 0.0059490 secs(回收时间)] [Times: user=0.00(用户耗时) sys=0.00(系统耗时), real=0.00 secs(实际耗时)]
- 老年代日志:
2014-07-18T16:19:16.794+0800: 1630.821: [GC 1630.821: [DefNew: 1005567K->111679K(1005568K), 0.9152360 secs]1631.736: [Tenured: 2573912K->1340650K(2574068K), 1.8511050 secs] 3122548K->1340650K(3579636K), [Perm : 17882K->17882K(21248K)], 2.7854350 secs] [Times: user=2.57 sys=0.22, real=2.79 secs]
jstat
监控JVM的状态 官网地址
GC监控
# 每1秒对60805进程进行一次统计
jstat -gcutil 60805 1000
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
微信公众号搜索“八多编程”,第一时间了解文章推送