Java学习之垃圾回收
垃圾回收(GC)
GC需要完成的三件事情:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?
为什么“GC自动化”之后还要研究GC?当需要排查各种内存溢出、内存泄漏问题时,当GC成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
计数算法
package com.xiaoyu.chap3.GC;
/**
* Created by xiaoyu on 16/4/4.
*
* testGC()执行后,objA和objB会不会被GC呢?
*/
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024*1024;
//搞个成员占点内存
private byte[] bigSize = new byte[2*_1MB];
public static void testGC(){
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
public static void main(String[] args) {
ReferenceCountingGC.testGC();
}
}
output:
[GC (Allocation Failure) 512K->440K(65024K), 0.0022170 secs]
[GC (System.gc()) 5037K->4656K(65536K), 0.0014100 secs]
[Full GC (System.gc()) 4656K->532K(65536K), 0.0074590 secs]
从输出结果上可以看出,jvm并没有因为这两个对象互相引用而不回收它们,说明用的不是计数算法。
可达性分析算法
可作为“GC Roots的对象“
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
引用
四种引用强度:
- 强引用:类似Object obj = new Object(),只要强引用还在,GC永远不会回收。
- 软引用:有用但非必须。必要时,第二次回收。
- 弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
- 虚引用:存在与否对对象无任何影响,唯一目的就是在这个对象被GC时收到一个系统通知。
live or die?
要真正宣告一个对象死亡,只要要经历两次标记过程。
第一次标记:如果可达性分析后发现没有与GC Roots相连接的引用链,就会被第一次标记。
第二次标记:如果第一次标记后,对象没有必要进行finalize()方法,则被第二次标记
何为没有必要进行finalize()?
- 对象没有覆盖finalize()方法
- finalize()方法已经被虚拟机调用过
如果”被认为有必要执行finalize()方法“,那么对象会被放置在F-Queue队列中,并由一个虚拟机生成的低优先级的Finalizer线程去执行它。
finalize()缺点:运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不建议覆盖。
判定一个常量是否无用:没有引用就是无用~
判定一个类是否无用:1.Java堆中不存在该类的任何实例;2.加载该类的ClassLoader已经被回收;3.没有反射机制访问该类
GC算法
标记-清除算法:
标记后清除。
缺点:效率低,空间碎片太多
复制算法:
将内存等分,一次只用一边,每次内存回收时,把存货的对象复制到另一块,然后回收一整块。
实现简单,运行高效,没有碎片问题
缺点:需要将内存缩小为原来的一般
标记-整理算法:
标记如同标记清楚算法,后续把所有存活的对象往一端移动,然后直接清理掉边界外的内存。
新生代死去的对象非常多,因此使用复制算法;老年代对象存活率高,因此使用标记算法。
垃圾收集器
HotSpot虚拟机的垃圾收集器:
Serial收集器
新生代虚拟机。
单线程的收集器,在它进行GC时,必须暂停其他所有的工作线程(所谓的Stop The World)
优点:简单而高效,由于没有线程交互的开销,因此专心做GC。。。对于运行在Client模式下的虚拟机是很好的选择。
新生代采用复制算法,暂停所有用户线程。(GC线程只有一个)
ParNew收集器
新生代虚拟机。
多线程版本的Serial收集器。
因为目前只有Serial和ParNew能和CMS收集器合作,因此它是很多Server模式的虚拟机的首选。
新生代采用复制算法,暂停所有用户线程。老年代使用标记-整理算法,暂停所有用户线程。(GC线程有多个)
Parallel Scavenge收集器
新生代收集器。
并行的多线程收集器,也是使用复制算法。
Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。
自适应调节策略
并发与并行:
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
- 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾手机程序运行于另一个CPU中。
Serial Old收集器
Serial收集器的老年代版本。使用标记-整理算法。
Parallel Old收集器
Parallel Scavenge收集器的老年代版本。
使用多线程和标记-整理算法。
用于和Parllel Scavenge收集器配合,达到“吞吐量优先”组合。
CMS收集器
CMS收集器是一种以获取最短回收停顿时间为目标的老年代收集器。
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
缺点:1.对CPU资源非常敏感,可能造成用户程序执行速度降低(采用过增加GC过程的时间,但是效果不好)
2.CMS收集器无法处理浮动垃圾,由于CMS并发处理过程中用户进程还在运行,部分垃圾出现在标记结束之后,因此得等待下次GC,即所谓“浮动垃圾”。
3.由于CMS使用的是“标记-清除”算法,因此会有大量的空间碎片。
G1收集器
当今收集器技术最前沿成果之一。
特点:
- 并行与并发:能充分利用多CPU,缩短STW停顿的时间,可以通过并发来让Java程序在GC时继续运行
- 分代收集:G1可以不需要其他收集器配合就独立管理整个GC堆,但它能够采取不同方式来处理不同状态的对象
- 空间整合:整体上看使用“标记-整理”算法,局部上看使用复制算法,因此不存在内存空间碎片问题
- 可预测的停顿:除了追求低停顿外,还能建立可预测的停顿时间模型
- 内存“化整为零“:将整个Java堆划分为多个大小相等的独立区域(Region),根据允许的收集时间优先收回价值最大的Region。通过Remembered Set技术来实现不同Region的对象引用问题
G1运作步骤:
- 初始标记(Initial Marking)
- 并发标记(Concurrent Marking)
- 最终标记(Final Marking)
- 筛选回收(Live Data Counting and Evacuation)
理解GC日志
33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm : 2999K->2999K(21248K)], 0.0150007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
最前面的数字:GC发生的时间,从虚拟机启动以来经过的秒数.
“[GC”和“[Full GC”:Full代表这次GC发生了STW.
“[Defnew”等等:GC发生的区域,不同的收集器有不同的名称
“3324K->152K(3712K)”:GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)
“3324K->152K(11904K)”:GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)
“0.0025925sec”:该内存区域GC所占用的时间
user、sys、real:用户态、内核态、操作开始到结束所经过的墙钟时间(包括各种如磁盘IO、等待线程阻塞等时间)
垃圾收集器参数总结
内存分配与回收策略
对象优先在Eden分配(使用Serial/SerialOld收集器组合)
package com.xiaoyu.chap3.GC;
/**
* Created by xiaoyu on 16/4/6.
* VM 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails -XX:SurvivorRation=8 UseSerialGC
*/
public class TestAllocation {
private static final int _1MB = 1024*1024;
public static void testAllocation(){
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB]; //出现一次Minor GC
}
public static void main(String[] args) {
TestAllocation.testAllocation();
}
}
output:
[GC (Allocation Failure) [DefNew: 7635K->533K(9216K), 0.0070160 secs] 7635K->6677K(19456K), 0.0070590 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4931K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 53% used [0x00000007bec00000, 0x00000007bf04ba80, 0x00000007bf400000)
from space 1024K, 52% used [0x00000007bf500000, 0x00000007bf5854b8, 0x00000007bf600000)
to space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
tenured generation total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
Metaspace used 3056K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 336K, capacity 386K, committed 512K, reserved 1048576K
从上面可以看出:
①前6MB数据分配到Eden区后,Eden区所剩的内存已经不足以分配allocation4了,因此发生MinorCG
②MinorGC之后虚拟机发现已有的3个2MB大小的对象无法放入Survivor空间,因此只能通过分配担保机制提前转移到老年代。
③GC结束后,allocation4被分配在Eden区,Survivor空闲,老年代被占用6MB(allocation1、2、3)
大对象直接进入老年代
package com.xiaoyu.chap3.GC;
/**
* Created by xiaoyu on 16/4/6.
* VM args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:PretenureSizeThreshold=3145728
* -XX:PretenureSizeThreshold参数令大小大于设定值的对象直接在老年代分配
*/
public class TestPretenureSizeThreshold {
private static final int _1MB = 1024*1024;
public static void testPretenureSizeThreshould(){
byte[] allocation;
allocation = new byte[4*_1MB];
}
public static void main(String[] args) {
testPretenureSizeThreshould();
}
}
output:
Heap
def new generation total 9216K, used 1655K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 20% used [0x00000007bec00000, 0x00000007bed9dd60, 0x00000007bf400000)
from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)
Metaspace used 3033K, capacity 4494K, committed 4864K, reserved 1056768K
class space used 334K, capacity 386K, committed 512K, reserved 1048576K
①可以发现,allocation对象直接被分配到了老年代中。
②PretenureSizeThreshold参数只对Serial和ParNew收集器有效!
长期存活的对象将进入老年代
package com.xiaoyu.chap3.GC;
/**
* Created by xiaoyu on 16/4/6.
*
* VM args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution -XX:UseSerialGC
* -XX:MaxTenuringThreshold来设置对象晋升老年代的年龄阈值
*/
public class TestTenuringThreshold {
private static final int _1MB = 1024*1024;
@SuppressWarnings("unused")
public static void testYenuringThreshold(){
byte[] allocation1,allocation2,allocation3;
allocation1 = new byte[_1MB/4];
//什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[_1MB*4];
allocation3 = new byte[_1MB*4];
allocation3 = null;
allocation3 = new byte[_1MB*4];
}
public static void main(String[] args) {
testYenuringThreshold();
}
}
①MaxTenuringThreshold=1时,allocation1对象在第二次GC时就会进入老年代,新生代已使用的内存GC后会就变为0KB
②MaxTenuringThreshold=15时,allocation1对象在第二次GC后还会留在Survivor。
动态对象年龄判断
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一般,年龄大于或等于该年龄的对象就可以直接进入老年代。
总结
内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素之一,虚拟机之所以提供多种不同的收集器以及提供大量的调节参数,是因为只有根据实际应用需求、实现方式选择最优的收集方式才能获取最高的性能。
Java学习之垃圾回收的更多相关文章
- java学习之 垃圾回收
垃圾回收器始终以一个较低优先级的后台进程进行垃圾的回收工作,这样不会影响程序的正常工作. 通常只有当内存到达用尽的边缘而程序需要分配新的内存空间时,垃圾回收器才会执行. 垃圾回收的条件:1,垃圾回收器 ...
- Java学习之垃圾回收机制
垃圾回收机制,依赖JRE和JVM,涉及操作系统中内存的分配与回收.依据所学,我猜想这种机制需要的数据结构是堆内存分配表(链),管理已分配和未分配的堆内存,对于已分配堆内存,需要知道由栈内存中的哪些变量 ...
- Java编程思想学习笔记_1(Java内存和垃圾回收)
1.Java中对象的存储数据的地方: 共有五个不同的地方可以存储数据. 1)寄存器.最快,因为位于处理器的内部,寄存器按需求分配,不能直接控制. 2)堆栈.位于通用RAM,通过堆栈指针可以从处理器那里 ...
- 高吞吐低延迟Java应用的垃圾回收优化
高吞吐低延迟Java应用的垃圾回收优化 高性能应用构成了现代网络的支柱.LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求.要优化用户体验,低延迟地响应这些请求非常重要. 比如说,用户经 ...
- 每日一问:讲讲 Java 虚拟机的垃圾回收
昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...
- JVM学习笔记——垃圾回收篇
JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...
- Java虚拟机之垃圾回收详解一
Java虚拟机之垃圾回收详解一 Java技术和JVM(Java虚拟机) 一.Java技术概述: Java是一门编程语言,是一种计算平台,是SUN公司于1995年首次发布.它是Java程序的技术基础,这 ...
- 【java虚拟机序列】java中的垃圾回收与内存分配策略
在[java虚拟机系列]java虚拟机系列之JVM总述中我们已经详细讲解过java中的内存模型,了解了关于JVM中内存管理的基本知识,接下来本博客将带领大家了解java中的垃圾回收与内存分配策略. 垃 ...
- java中存在垃圾回收机制,但是还会有内存泄漏的问题,原因是
答案是肯定的,但不能拿这一句回答面试官的问题.分析:JAVA是支持垃圾回收机制的,在这样的一个背景下,内存泄露又被称为“无意识的对象保持”.如果一个对象引用被无意识地保留下来,那么垃圾回收器不仅不会处 ...
随机推荐
- uva 10494 - If We Were a Child Again 大数除法和取余
uva 10494 - If We Were a Child Again If We Were a Child Again Input: standard inputOutput: standard ...
- C# 各版本的新特性
http://www.cnblogs.com/JeffreySun/archive/2012/11/14/2770211.html
- call to unavailable function system not available on ios 解决方案
编译时报错:call to unavailable function system not available on iOS 原因:iOS11已经将system删除 解决方案:system(comma ...
- 将坐标转化为与X轴正半轴夹角模板
//还需加PI 和 mabs 函数 double chg(double x,double y) { double tmps; )<1e-) { ) tmps=90.0; else tmps=27 ...
- 【BZOJ4870】[Shoi2017]组合数问题 动态规划(矩阵乘法)
[BZOJ4870][Shoi2017]组合数问题 Description Input 第一行有四个整数 n, p, k, r,所有整数含义见问题描述. 1 ≤ n ≤ 10^9, 0 ≤ r < ...
- [LintCode] 二叉树的前序遍历
The recursive solution is trivial and I omit it here. Iterative Solution using Stack (O(n) time and ...
- textarea输入字符有限制
function limitedNumberOfInputCharacters(limitedNumber, string){ var strLength = 0; if(string !== nul ...
- mvn命令上传jar
开发过程中涉及到下载第三SDK包,而本身项目是基于gradle的,所以为了项目中使用sdk包,需要将包加入到自己的仓库 1.利用nexus创建自己的第三方库thirdparty 类型hosted 2. ...
- IO流入门-第三章-FileInputStream_FileOutputStream复制
利用FileInputStream和FileOutputStreamj进行复制粘贴 /* 文件复制粘贴 */ import java.io.*; public class FileInput_Outp ...
- Spring 框架整合Struts2 框架和 Hibernate 框架
1. Spring 框架整合 Struts2 框架 // [第一种整合方式(不推荐)](http://www.cnblogs.com/linkworld/p/7718274.html) // 从 Se ...