java中GC回收和内存分配

来源:互联网 发布:win10网络文件夹为空 编辑:程序博客网 时间:2024/05/29 04:45

      前文讲过垃圾回收主要管理的区域是堆内存,而堆内存主要是存放对象实例,那么要对对象实例进行回收前,必须确定该对象是否还有对他的引用,也就是该对象在内存中是否已经死了?

引用计数算法:

      有很多语言采用的是引用计数算法,每当有一个地方引用它时,引用计数就加1,当引用计数为0时表示该实例不再被使用,可以被回收。通常情况下引用计数是一个不错的算法,如Object-C,python等都使用了这种算法管理内存,但是java中没有使用引用计数算法作为内存管理的算法,最主要的原因它难以解决循环引用问题,例如:一个java方法中创建两个对象实例,每个对象实例都引用对方,当方法执行结束后,方法中创建的对象应该被回收,但是因为循环引用,引用计数不为0,所以就回收不了这两个对象实例

 可达性分析算法:

      这种算法的基本思路是通过一系列的GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象没一条引用链到达GC Roots,则说明此对象可以被回收。java中GC Roots对象有以下几种:

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

   2)方法区中类静态属性引用的对象

   3)方法区中常量引用的对象

   4)本地方法栈中JNI(Native方法)引用的对象

标记-清除算法:

      最基础的收集算法是“标记-清除”(mark-sweep)算法,算法分为标记阶段和清除阶段,首先标记出需要回收的对象,这种算法不足之处:效率低,产生大量空间碎片,导致以后程序运行期间分配大对象时候,无法找到连续的空间提前触发有一次垃圾回收

复制算法:

     将内存分为两块相等空间大小的区域,每次只是用其中一块,当内存用完时,将引用链上对象复制到未使用的内存空间,然后将使用过的内存空间一次清理掉,就不用再考虑空间碎片的问题了,实现简单,运行高效,就是内存利用率原来的一半,代价太高,现在虚拟机都采用这种收集算法回收新生代,IBM公司研究,新生代的98%对象都是朝生夕死,所以不需要按照1:1的比例取分配内存,而是将内存分配为一块较大的Eden空间和两块较小的Survivor空间,每次使用Elen和一块Survivor空间,回收时候,将存活对象复制到未使用的Survivor空间,一次清理掉Eden和使用的Survivor空间,HotSpot默认比例Eden:Survivor=8:1,即新生代中可用空间为原来空间的90%,10%是浪费的。但是并不是每次都有少于10%的存活对象,如果存活对象多余10%,则依赖其他内存进行分配担保


标记-整理算法:

     复制算法在对象存活率高的情况下需要进行较多的复制,效率变低,还可能需要额外的空间进行分配担保,所以老年代一般不采用这种算法。

根据老年代的特点,有人提出标记-整理算法,同标记-清除算法一样,但是对于回收对象不直接清除,而是向一端移动,然后清理掉端边界意外的内存,当前虚拟机都是待用分代算法,对于新生代采用复制算法,老年代采用标记-整理/标记-清除算法。

HotSpot算法实现:

    从可达性分析中GC Roots查找引用链操作,可能仅仅方法区就有数百兆,如果逐个检查里面的引用,必然很慢。还有,这个分析过程必须在一个保证一致性的快照中进行,否则无法保证分析结果的正确性,这就导致在查找过程必须停顿所有线程(sun称之为stop the world),在HotSpot的实现中,并不需要对所有的全局引用位置和执行上下文(jvm栈本地变量表)全部检查完,而是使用一组OopMap的数据结构达到这个目的,在类加载完成时候,把对象内什么偏移量上是什么类型数据计算出来,在JIT编译过程中,也会在特定位置记录下栈和寄存器中那些位置是引用,这样在扫描时可以直接得知这些信息了。

     在OopMap的帮助下,HotSpot可以快速的准确的完成GC Roots枚举,可是问题也会随之而来,OopMap内容变化的指令很多,如果每一个指令都生成OopMap,那么需要大量额外空间,GC成本将非常高,实际上,HotSpot确实没有为每条指令都生成OopMap,只是在特定的位置生成这些信息,这些位置成为安全点,只有到安全点时候才执行GC。当GC时如何让所有线程都跑到安全点,有两种方法:1抢先式终端 2主动式中断

抢先试中断:GC时,中断所有线程,然后对于没有达到安全点的线程,恢复并让其跑到安全点上

主动式中断:GC时,不主动中断线程,而是设置一个标志,然后所有线程主动轮询这个标志,发现中断标志位真,就挂起

0 0
原创粉丝点击