垃圾收集 与自动内存管理

一、概述


    在Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈这三个区域随着线程而生,随着线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作,每一个栈帧中分配多少内存基本上都是在类结够确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不用过多考虑回收的问题。而Java堆和方法区则不一样,一个接口中的多个类需要内存可能不一样,一个方法中的多个分支需要的内存可能也不一样只有在程序处于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器关注的就是这部分的内容。


二、喂,你这个对象还活着吗



堆中存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定哪些对象还存活着,哪些已经死去,判断方法有:

①    引用计数算法(简单、效率高,但是无法解决对象之间相互引用的关系,主流Java虚拟机中未采取此种方法):给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1,任何时候计数器值为0时对象就是不可能再被使用的。

②    可达性算法:通过一系列的称为“GC Roots”的对象作为起点,从这些结点的起始点开始向下搜索,搜索走过的路径称为引用链,每当一个对象到GC Roots没有任何引用链相连时则证明此对象不可达。不可达的对象并不是立即死亡,而是会经过至少两次标记过程:在对象进行可达性分析后没有发现与GC Roots想连接的引用链会被第一次标记并执行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖此方法或者已经被虚拟机调用过,则把这两种情况都视为“没有必要执行”,可以回收。

在Java语言中,可作为GC Root的对象包括:

①     虚拟机栈(帧栈中的本地变量表)中引用的对象

②     方法区中类静态属性引用对象

③     方法区中常量引用的对象

④     本地方法栈中JNI(即一般说的Native方法)引用的对象


引用类型:

我们希望内存中增加一类对象,当内存空间足够时则能保存在内存之中,如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。

①    强引用(Strong Reference):指在程序代码中普遍存在的,类似Object obj = newObject()这类的引用,只要强引用还存在,就不会被回收掉的引用对象。

②    软引用(Soft Reference):描述一些还有用但并非必须的对象。对于软引用关联的对象,在系统将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

③    弱引用(Weak Reference):也是用来描述非必须对象单强度更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当前垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

④    虚引用(Phantom Reference):最弱的引用,当一个对象为虚引用时唯一目的就是能在这个对象被收集器回收时收到一个系统通知。无法通过虚引用来取得一个对象实例,虚引用完全不会对其生存时间造成影响。


确定一个对象真实死亡

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这个时候他们暂时处于缓刑阶段,要宣告一个对象真正死亡至少需要经过两次标记:如果对象在进行可达性分析后没有发现与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为"没有必要执行"。如果在下次回收前在finalize()中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可,这样 ,它就可以被重新拯救。


三、垃圾收集算法


①     标记 – 清除算法

首先标记出所有需要回收的对象,在标记完成后同意回收所有被标记的对象。

不足:效率低,标记和清除效率均不高,另一个是空间问题,标记清除后会产生大量不连续的内存碎片,控件碎片太多可能会导致以后在程序运行过程中需要分配较大的对象时无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作。

②     复制算法

把可用内存按容量划分为大小相等的两块,每次只用其中的一块,当一块用完了,就把还存活对象复制到另一快上面去,然后再把已使用的对象清除掉。

每次回收都是对半区进行回收,内存分配时也不用考率内存碎片的问题,只需要移动堆顶指针按顺序分配即可,实现简单,效率高,但是这种算法代价是将内存缩小,代价太高。

HotSpot:把内存分配为一块较大的Eden和两块较小的Survivor控件,每次使用Eden和一块Survivor,当回收时将Eden和Survivor中海存活的对象一次复制到另一块Survivor中,最好清理掉Eden和刚才用过的Survivor空间,其比例默认8:1,也就是新生代中可用内存空间为整个新生代容量的90%(80%+10%),浪费10%的内存。(如果另外一块Survivor上没有足够的空间存放上一次新生代收集下来的存活对象,则将这些对象直接通过分配担保机制进入老年代)

③     标记整理法

首先标记出需要回收的对象,其次让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

④     分代收集算法

这种算法并没有什么新的思想,只是根据各个年代的特点采用最适当的收集算法。

在新生代中,每次垃圾收集时都会发现有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法进行回收


四、HotSpot虚拟机算法实现



GC停顿:可达性分析工作必须在一个能确保一致性快照中进行——“一致性”是指在整个这项分析(根节点枚举为例)期间整个执行系统看起来就像是冻结在某个时间点,不可以出现分析过程中对象引用关系还在不断变化的情况,该点不满足的话分析准确性就无法得到保证。这点是导致GC进行时必须停顿所有Java执行线程(Stop the world)的其中一个重要原因。

目前主流Java虚拟机都采用准确式GC,所以当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用,在HotSpot中,是使用一组称为OopMap的数据结构来达到这个目的,在类加载的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译中也会在特定的位置记录下栈和寄存器中哪些位置是什么引用,这样,GC在扫描时就可以直接得知这些信息了。

安全点:程序执行时并非所有位置都可以停顿下来进行GC,而是只有在到达安全点才可以尽心GC。安全点的选定以“是否具有让程序长时间执行的特征”为标准进行选定。一般方法调用、循环跳转、异常跳转等具有这些功能的指令才会产生safePoint。

如何确定GC时线程(不包括JNI)都跑到安全点:抢先式中断(基本无使用)和

主动式中断:GC停顿时不主动对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时则把自己挂起。轮询标志的地方与安全点是重合的,另外在加上创建对象需要分配内存的地方。

安全区域:为了解决程序不执行时(典型的如线程处于Sleep或者Blocked状态,这时线程无法响应JVM的中断请求,“走到”安全的地方去中断挂起,JVM也显然不可能等待线程重新分配CPU时间)进行GC。

在一段代码片段中,引用关系不会发生变化,在这个区域中的任何一段地方开始GC都是安全的。


五、理解GC日志


GC日志

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

0.0031680 secs]


100.667 : [Full GC [Tenured: 0K->201K (10240K),0.0150007secs ] 4603K->201K(19456K),[Perm : 2999K->2999K(21248K)],0.0150007secs][Times: user =0.01 sys = 0.00 ,real = 0.02secs]

其中 33.125 和100.667 表示GC发生的时间,这个数字的含义是从Java虚拟机启动以来经过的秒数;

GC和FullGC表示GC的停顿类型,如果有Full则表示GC是发生了Stop-the-world的;

DefNew和Tenured、Perm表示GC发生的区域;

3324k->152k代表GC前和GC后的内存使用容量,


六、自动内存管理


 

自动内存管理解决两个问题:给对象分配内存以及回收分配给对象的内存

多数情况下,对象在新生代Eden区中分配,当Eden中没有足够的空间进行分配时,虚拟机将发起一起Minor GC(指新生代的垃圾收集动作,发生非常频繁,回收较快。新生代采用复制算法手机内存)。

大对象直接进入老年代,大对象指需要大量连续内存空间的Java对象,最典型的是很长的字符串以及数组。虚拟机提供参数 –XX:PretenureSizeThreshold参数,大于这个参数的对象直接在老年代进行分配。这样做可以避免在Eden区以及两个Survivor区之间发生大量内存的复制。

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

空间分配担保:在Minor GC前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立,则Minor GC可以确保是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许则会继续检查老年代最大可利用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试进行一次GC,如果小于,或者上述值设为false,则进行一次Full GC。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值