垃圾收集器与内存分配策略

垃圾收集的对象

程序计数器,虚拟机栈,本地方法栈随着线程而生,随着线程死,栈中的栈帧在方法执行后会自动出栈,所以这几部分不需要垃圾回收。
而堆和方法区具有不确定性,内存的分配回收都是动态的,需要垃圾回收机制。
堆中主要回收对象,方法区主要回收废弃的常量和不再使用的类。


可达性算法

虚拟机是通过可达性算法判断对象是否存活的
可达性算法就是以GC Root为根节点,根据引用关系向下搜索,搜索走过的路程称为引用链,当对象没有被任何链引用时,则该对象已死
可以被称为GC Root的对象
·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
·所有被同步锁(synchronized关键字)持有的对象。
·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。


OopMap

oopmap存放了对象引用信息,即在栈帧和寄存器上的哪些位置存放了对象引用,使得垃圾回收过程能够加快GC root的枚举。


安全点

生成oopmap的阶段叫安全点,只有全部线程执行到安全点才能进行暂停进行垃圾回收。安全点是指在Java程序执行过程中的某个特定位置,此时所有线程都处于安全状态,即没有执行关键的代码片段,如循环、方法调用等。在安全点上,垃圾回收器可以安全地进行垃圾回收操作,而不会对正在执行的线程产生影响。


安全区域

要是业务线程都不执行(业务线程处于 Sleep 或者是 Blocked 状态),那么程序就没办法进入安全点,对于这种情况,就必须引入安全区域。
安全区域是指能够确保在某一段代码片段之中, 引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区城看作被扩展拉伸了的安全点。


回收方法区

回收废弃的常量和不再使用的类
常量:加入曾经有一个字符串“java”加入了常量池,但是限制系统中没有一个对象的值是java,即没有任何字符串对象引用“java”这个常量,并且虚拟机中没有其他地方引用这个字面量,就会被回收。
类:判断类型是否属于不再被使用的类需要满足三个条件:
1该类的所有实例被回收
2该类的类加载器被回收
3该类的java.lang.Class对象没有被引用,即不能在其他地方通过反射访问该类的方法。


分代收集理论

两个假说:
●弱分代假说:绝大多数对象都是朝生夕灭的。
●强分代假说:熬过越多次垃圾收集过程的对象就越难消亡。
所以将java堆分为两个区域:新生代和老年代。新生代每次垃圾收集时都用大批对象死亡,所以我们着重关注标记那些没有死亡的对象。老年代代的对象很难死,所以我们可以以较低频率去回收老年代。此外,在新生代中如果垃圾收集了多次都没死的话,就会放到老年代中。

 

记忆集与卡表

记忆集是用来解决区域化(分为新生代,老年代)收集算法中对象跨代的问题,即新生代的对象有可能被老年代所引用,那么在垃圾收集新生代的对象时是不是要遍历老年代或者其他非收集区域的对象呢,这会增加大量开销,所以我们使用记忆集来保存那些非收集区域(即在这次垃圾收集中没有收集的区域,例如新生代收集时老年代就不会收集)的具有引用关系的对象,这样就不用扫描整个非收集区域了。
卡表就是记忆集的具体实现,使用类似哈希表的key-value存储。卡表将老年代和其他非收集区域划分为多个卡页,每个卡页对应卡表中的一个元素,当收集区域的对象被卡页中的对象引用时,卡页就会被标记成脏页。
标记为脏页依靠写屏障实现,类似于aop,在每次赋值操作后写屏障触发,更新卡表,标记成脏。
HotSpot的算法细节实现


并发的可达性分析

主流的垃圾收集器基本上都是依靠可达性分析算法判断对象是否存活,可达性分析算法要求全过程都基于一个能保障一致性的快照中才能进行分析,意味着在分析时要冻结用户线程的运行。
分析可达性即检查这个对象有没有被其他对象引用,在并发情况下可能会出现问题。
我们采用三色标记来表示对象状态
1白色,表示对象还没有被垃圾收集器访问过。在还没有开始分析时全部对象都是白色的,在分析结束后,不可达的对象也是白色的。
2黑色,表示被垃圾收集器访问过,且这个对象的所有引用都被访问过。表示这个对象是安全存活的。
3灰色,指这个对象被访问了,但还有引用没有被访问。
如果在并发情况下(即用户线程和收集器一起工作,有可能会出现以下问题,一个是把应该消亡的对象标记为存活(可以容忍),另一种是把应该存活的对象错误地标为消亡(不能容忍),称为对象消失。
发生对象消失需要满足两个条件,一个是赋值器删除了灰色对象到白色对象的引用,另一个是有其他黑色对象到白色对象的新引用。原理是黑色对象说明它的引用已经扫描完了,这个新引用不会被再扫了,而又删除了灰色对象到这个对象的引用,就会导致这个对象本来有其他对象引用了它,但却扫描不到它。
解决办法就是任意破坏其中一个条件。
1原始快照,解决删除灰色对象到白色对象的引用条件。就是记录这个引用,然后把这个引用的灰色对象为根,重新扫描,这样就不用担心被引用的对象不会被扫描到。
2增量更新,解决黑色对象到白色对象的新引用条件。将新插入的引用记录下来,在第一次扫描结束后,再将有这个引用的黑色对象为根再扫描一次,这样就可以扫描到这个新引用的对象了。


内存分配与回收策略

对象优先在Eden分配

新生代中Eden和Survivor的比率一般为8:1,假设新生代有10MB,那么Eden占8MB,两个Survivor各占1MB,分配内存时在Eden和其中一个Survivor中分配,发生垃圾回收之后将没有被回收的对象放在另一个survivor中(标记-复制算法),如果这个survivor中放不下,通过分配担保机制提前转移到老年代中去。

大对象直接进入老年代

大对象:需要大量连续内存空间的java对象,例如byte[] b = new byte[2*_1MB]
HotSpot虚拟机提供了一个参数:PretenureSizeThreshold,指定大于该设计值的对象直接在老年代中分配,这样可以避免Eden区以及两个Survivor区之间来回复制,浪费性能(具体就是假设放在新生区,如果这个对象还没有死的话,由于标记-复制算法,会来回在一个Eden和Survivor到另一个Survivor中复制。

长期存活的对象将进入老年代

每个对象都定义了一个对象年龄(Age)计数器,存储在对象头。对象通常在Eden中产生,在GC后没死的话放到Survivor中,并将初始年龄设为1,对象中Survivor区中每熬过一次GC,就加1岁,当年龄增加到一定程度,就放到老年区,默认是15岁。

动态年龄判定

HotSpot虚拟机并不是永远要求对象的年龄要达到多少岁才能进入老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor的一半,年龄大于或等于该对象就可以直接进入老年代。

空间分配担保

在发生MinorGC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间(不然如果新生代的全部对象都到年龄或者survivor满了的话老年代放不下,如果担保失败,只能进行FullGC,FullGC会导致停顿时间变长 ),如果是,这次GC才是安全的。如果不是,虚拟机通过查看HandlePronitionFaileure查看是否允许担保失败。如果允许失败,则检查老年代的最大连续可用空间是否大于之前晋升到老年代的对象的平均大小,如果小于,还是进行MinorGc,如果小于,则进行FullGc。
所以有两种情况会导致FullGc,一种是没有打开空间分配担保并且老年代的连续可用空间小于新生代的对象空间,另一种是允许了担保,但现在老年代的最大连续可用空间小于历届新生代上升到老年代的平均对象大小。

 

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值