深入理解jvm — GC篇

java自动管理内存主要解决的两个问题:
1、给对象分配内存,上篇博客已经介绍过了http://blog.csdn.net/ying1414058425/article/details/60141543
2、回收分配给对象的内存,下面我将一步一步的进行分析

一、概述

垃圾收集GC(Garbage Colletion)主要回收的区域是java堆和方法区
因为程序计数器、虚拟机栈、本地方法栈三个区域的生命周期是和线程一样的
他们的内存分配和具有确定性,在类结构确定下来就已知的,所以不需要过多的考虑回收问题,因为方法结束或者线程结束,内存自然就回收了
java堆和方法区,只有在运行期间才知道会创建哪些对象,内存分配和回收都是动态的

二、对象已死吗?

在堆里放着Java几乎所有的对象实例,垃圾回收器在对堆进行回收前,第一件事情就是确定对象中有哪些还存活,哪些已经死去(即不可能再被任何途径使用的对象)

判断对象是否存活的算法:
1、引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时计数器值就加一,当引用失效时计数器值就减一,任何时刻计数器为0时的对象就是不可能再被使用的
计数算法的问题:
主流的java虚拟机没有选用引用计数算法来管理内存,主要是因为他很难解决对象之间相互循环引用的问题
例如对象A和对象B 互相引用着对方 ,及时当对象都赋值为null ,因为他们的引用计数都不为0,所以引用计数器无法通知GC收集器来回收他们

2、可达性分析算法
主流的商用语言(java、c#)的主流实现中都是通过可达性分析(Reachability Analysis)来判断对象是否存活
算法思路:
通过一系列的成为“GC Roots”的对象作为起始起点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的

在java语言中,可作为GC Roots的对象:
虚拟机栈中(帧栈中本地变量表)中引用的对象
方法区中类静态属性应用的对象
方法区中常量引用的对象
本地方法栈中JNI(Native方法)中引用的对象

再说一下引用
强引用: 类似 Object obj = new Object() 只要强引用在 Object就不会被回收
软引用:用SoftReference关键字,内存溢出之前,会进行回收
弱引用:比软引用更弱一点,用WeekReference关键字,垃圾回收器工作时,无论内存是否足够,都会回收
虚引用:最弱的引用,用PhantomReference关键字,不会对被引用的对象产生任何影响,无法通过虚引用获取到对象实例,唯一作用是能在对象被回收是收到一个系统通知

堆中对象的回收
一个对象被回收,要标记两次
第一次,没有与GC Roots相连的引用链,那么他会进行第二次筛选,看他是否有必要执行finalize()方法
如果此对象没有覆盖finalize()方法 ,或者finalize()方法被虚拟机调用过了,就不必执行
如果有必要执行,那么这个对象会放在F-Queue队列中
第二次,GC对F-Queue中的对象进行标记,如果在finalize()方法中,对象有被引用就会被回收
如果对象与引用链上的对象建立关联,他就会被移除即将回收的集合
建议:不用使用finalize()方法,对象没有在GC Roots的引用链中,就会被回收,不要用finalize进行自救

方法区的回收
方法区GC主要收集两部分内容:废弃常量和无用的类
废弃常量:例如一个常量没有任何引用,他就会被清除里常量池
无用的类:例如一个类的实例全部被回收,加载该类的ClassLoader被回收,该类对用的java.lang.Class对象没有在任何地方被引用

三、垃圾收集算法

1、标记 - 清除算法
先将要回收的内存标记出来然后再清除
不足:效率不高,清除后会产生大量不连续的内存碎片

2、 复制算法
将内存按容量划分为大小相等的两块,每次使用一块,当一块内存用完了,就将还存活的对象复制到另一块上,然后将已使用过的内存清理掉
优点:不会产生内存碎片,实现简单,运行高效
不足:将内存缩小为原来一半,代价太高,并且如果对象的存活率较高时就会进行较多次的复制,效率会变低
现在的商业虚拟机较多使用这种方法,但是内存没平分,因为绝大部分对象都是会被回收的,所以比例可以为8:1,8为每次存满了,清空,然后再复用,1为存活的对象暂时储存的地方,如果存活的对象多余1的空间,可以依赖其他内存进行分配担保

3、标记-整理算法
比标记 - 清除算法多了一步整理,就是先标记清除,然后存活的对象整理到一起,避免了内存碎片的问题

4、分代收集算法
根据对象存活周期的不同,将内存划分为几块,一般把java堆分为新生代和老生代
在新生代,对象存活率低,采用复制算法
在老生代,对象存活率高,使用标记清理或标记整理算法

四、HotSpot虚拟机的GC算法实现

1、枚举根节点
GC Roots节主要是:全局引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)
枚举根节点,不允许对象引用关系发生变化,不然准确性无法保证,这导致GC进行时必须停顿所有java线程
很多应用的方法区中引用太多,要逐个检查的话,会消耗很多时间
在HotSpot中会通过OopMap记录下来数据类型,JIT也会记录下来哪些是引用,使得GC在在扫描时就可以知道这些信息

2、安全点
只有在安全点,才能停顿下来GC
安全点的选定标准是,程序长时间的执行,例如方法调用、循环、异常等
GC时,怎么样让所有的线程都跑到安全点再停顿下来,有两种方法
第一种 抢先式中断:
GC时,中断所有线程,如果有线程不在安全点上,就恢复线程,让他执行到安全点上,再中断他(几乎不用这种方法)
第二种 主动式中断:
设置轮询标志,标志点为安全点和创建对象分配内存的地方,各个线程会主动轮询这个标志,发现标志为真时就中断挂起

3、安全区域
安全点SafePoint不足:当程序处于Sleep或Bloked状态,就不会分配cup,就无法响应JVM的中断请求
安全区域Safe Region:一段代码之中,引用关系不会发生变化,在此区域的任何地方GC都是安全的
GC时不用管进入安全区域的线程,线程会在离开安全区域前检查自己是否要GC或者根节点枚举,如果没完成,必要要等待安全离开的命令后才可以离开

4、垃圾收集器
下面简单介绍几个收集器

Serial收集器
Serial收集器是单线程的,GC时必须暂停其他的所有工作线程,对于“Stop The World”的不良体验,VM的设计者说“你妈妈给你打扫房间时,肯定会让你老老实实的带着,如果她一边打扫,你一边扔纸屑,这房间还能打扫完?”

ParNew收集器
是Serial收集器的多线程版本

Parallel Scavenge 收集器
他是一个新生代收、使用了复制算法、并行多线程的收集器
他的设计目标是,达到一个可控的吞吐量 (吞吐量 = 运行代码的时间 / (运行代码的时间 +垃圾回收的时间))
他提供了两个参数用于精确控制吞吐量:控制最大垃圾停顿时间 、直接设置吞吐量大小

Serial Old 收集器
他是Serial收集器的老年代版本,使用标记整理算法

Parallel Ord 收集器
他是Parallel Scavenge 收集器的老年代版本,使用多线程和标记整理算法

CMS收集器
他的设计目标是,GC时间最短
CMS是基于标记清除算法,但是更复杂一点,他分为4步

初始标记:标记GC Roots 直接关联的对象,速度很快,需要Stop The World
并发标记:进行GC Roots
重新标记:修改并发标记期间因程序继续运行而导致标记产生变动的那一部分对象,需要Stop The World
并发清除

优点:响应速度快,用户体验好,因为耗时最长的并发标记和并发清除过程中,收集器的线程可以和用户线程一起工作,所以他的GC停顿时间还是很短的
不足:并发阶段,GC会占用一部分CPU资源,导致引用程序变慢,总吞吐量降低
CMS无法处理浮动垃圾可能导致另一次Full GC,浮动垃圾为出现在标记过程之后的垃圾
CMS采用标记清除算法,会产生过多的碎片。可以通过设置参数,在FullGC时之前进行碎片整理,或者几次FullGC后进行一次整理

G1 收集器
面向服务端应用的垃圾收集器,他的使命是替换掉HotSopt JDK1.5中的CMS
特点:
并发与并行:充分利用多CPU、多核环境的硬件优势缩短GC停顿时间
分代收集:分为新生代和老生代
空间整合:整体基于标记整理算法,局部使用复制算法
可预测停顿:可明确指定在长度为M的时间片段里,停顿的时间不超过N毫秒

G1回收步骤
初始标记、并发标记、最终标记、筛选回收

四、理解GC日志

例如:

33.125 : [ GC DefNew  :   3324K->152k( 3712K ) , 0.0025925 secs]  3324K-> 125K(11904K),0.00316 secs

33.125: GC发生时间,值为JVM启动的秒数
GC : GC停顿的类型, 可以为GC或Full GC
DefNew:GC发成的区域, 名称由收集器决定
3324K->152k( 3712K ): (使用内存)->GC后使用内存(总内存)
0.0025925 secs :GC时间
3324K-> 125K(11904K):(Java堆使用容量)->GC后Java堆使用容量(Java堆总容量)
0.00316 secs : GC时间

五、关于新生代和老生代

1、对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配,当Eden区中没有足够的空间时,JVM就会发起一次Minor GC
新生代GC(Minor GC) :发生在新生代的GC,因为Java对象大多都是朝生夕灭,所以Minor GC非常频繁,一半回收速度也较快
老生代GC(Major GC / Full GC) : 发生在老生代的GC,出现了Major GC,经常会伴随至少一次的Minor GC,Major GC比Minor GC速度慢10倍以上

2、大对象直接进入老年代
大对象:需要大量连续内存空间的对象
JVM提供了一个参数,使大于这个设置的对象直接在老年代分配,避免使用复制算法时,产生大量的内存复制

3、长期存活的对象将进入老年代
JVM采用了分代收集的思想来管理内存,他会给每个对象定义一个对象年龄计数器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值