《深入理解JVM》第三章 垃圾收集器与内存分配策略(对象已死吗 ? )

版权声明:版权为ZZQ所有 https://blog.csdn.net/qq_39148187/article/details/81984792

对象已死吗 ?

  • 引用计数算法

给对象添加一个引用计数器,当有一个地方引用他就给他+1 , 如果有一个地方的引用失效就-1 ,实现简单,效率高,微软公司的COM(component object model) 技术,使用ActionScript3的Flashplaye ,python语言在游戏脚本领域被广泛使用的Squirrel 中都使用了引用计数器算法对内存进行管理,但是主流的java 虚拟机中没有采用这种算法,原因式,很难解决对象之间的互相循环引用问题

  • 可达性算法

Java C# lisp 都是使用的可达性分析算法,判断对象是否存活,基本思路就式通过一系列成为 gc roots 的对象为起点,从节点开始往下搜索,搜索走过的路径成为引用链,当一个对象到gc root 没有任何的引用链相链接,呢就证明了这个对象是不可用的

Java 中gcroot对象包括

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

  2. 方法区中的类静态属性引用的对象 (方法区中放常量,静态变量,类信息,编译器编译后的代码数据在hotstap 中叫做永久代)

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

本地方法栈中jni native方法引用的对象

  • 再谈引用

在jdk1.2 以前,对象通过reference 类型的变量存储的数指,代表是另一块内存中其实的地址,这块内存代表着一个引用,这样一个对象只有两种状态引用和没有被引用,

我们要实现如果内存充足,把一些对象留在内存中,如果不充足,就回收

1.2后 java 对引用的概念扩充

Strong reference 强引用

Soft reference 软引用

Weak reference 软引用

Phantom reference 虚引用

引用强度一次减弱

  1. Object oj = new object() 这种是强引用,gc永远不会回收调

  2. 软引用soft reference 是描述一些还有用但并非必须用的对象,对于软引用的关联对象在系统将要发生内存溢出之前将会把这些对象进入回收范围之中进行二次回收,如果这次回收没有足够的内存会抛出内存溢出异常

  3. 软引用,描述对象是非必须的,他的强度被soft 又弱一点,卑弱引用关联的对象,只能生存到下一次垃圾回收,gc 回收的时候,无论内存是否够用都给回收调

  4. 虚引用,虚幻引用,是最弱的,一个对象那个是否有虚引用的存在完全不会对他的生存时间造成影响,也无法通过虚引用获取一个对象的实例化,为一个对象设置虚医用的目的是能在这个对象被收集器回收的时候收到一个系统通知

生存还是死亡?
在可达性算法中一个不可达的对象,不是非死不可,这个时候他们暂时处于缓刑截断,要真正宣布一个对象的死亡,要经过两次标记,如果这个对象在进行可达性分析之后发信啊没有于gcroots 相连接的引用链,那么他会被第一个次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize() 方法的时候,或者finalize方法被虚拟机调用过,虚拟机i将这两种情况视为没有必要执行

如果让对象视为没有必要执行的finalize方法,那么这个对象会被放在f-queue 队列中,在稍后的一个由虚拟机自动建立起来,优先级低的finalizer线程区执行他,这里的执行是指触发这个方法,但是并不承诺会等待运行结束,这样做的原因是,如果一个对象在finalize方法中执行缓慢,或者发生了死循环,会导致f-quecue 列队中的其他对象处于等待状态,这样内存回收系统就容易崩溃,finalize方法是对象逃离死亡命运的最后一次机会,稍后gc 会对f-queue中的对象进行二次小规模的标记,如果对象要在finalize 中成功拯救自己,重新于引用链链接上,自己被别的对象引用,那么他在第二次标记的时候它会被溢出列队中,如果对象没有逃脱就真的彻底被gc回收死亡

  • 回收方法区

方法区是会被回收的,只是回收的条件比较苛刻, 常量池中的常量,类接口,方法,字段的符号引用如果没有被引用就会被清除到常量池外

方法区回收的苛刻的主要是无用类的回收

  1. 该类的所有的实例都已经呗回收,在java 堆中不存在改类的任何实例

  2. 加载该类的classloader类呗回收

  3. 该类对应的java。Lang。Class 对象没有任何对象引用,无法在任何地方通过反射访问类的对象

满足上面的三个条件,才仅仅可以回收,不是像对象那样不使用就必然会被回收,是否堆类进行回收,jvm 提供了 参数进行操作

垃圾回收算法

  • 标记–清除

首先年标记出需要回收的对象,在标记玩之后统一的进行回收所有被标记的对象,这个是最基础的收集算法, 之后的算法都是在他的基础上进行改造,这个算法有两个不足,效率不高,标记和清除的效率都不高,空间问题,清除之后会产生很多的不连续的内存空间碎片,这个样会导致,如果分配大的对象无法找到连续的内存进行分配,

  • 复制算法

把内存分为两个区域,一个内存区域用完把存活的对象复制到另一个内存区域上,然后清空内存区域

现在商业虚拟机都采用这种方式回收新生代,ibm公司研究表明, 新生代的对象98%都是死的很快,所以不用按照1:1 方式进行分配内存

把内存分为一大块的Eden 和Survivor 2小块,每次使用eden 和其中一块Survivor,当回收的时候,把eden 和survivor 中存活的对象复制到另一块survivor 内存上,然后清除掉enden 和survivor 空间,hotstop默认eden和survivor 的大小比例是8:1 在整个新生代中可用内存是90% ,只有10%会被浪费,当survivor 内存不够用的时候还要依赖其他的内存进行分配担保,担保以后再说

  • 标记整理

复制收集算法在对象存活率高的时候进行较多的复制操作,效率会变低,更关键的是,如果不想浪费50%空间,就需要有额外的空间进行份额担保,以应对使用的内存中所有的对象都是100%存货的极端条件,所以老年但不适用这种算法

根据老年代的特点有人提出另一种 标记–整理(mark-compact) 算法,标记过程和标记清除的过程算法一样, 标记之后不是清除,而是让所有的存活对象都向一端移动,然后清除端表姐以外的内存

  • 分代收集

分代收集算法,只是根据对象存活的周期不同话分内存为几块,一般java 把堆划分为新生代,和老年代,这样就可以根据各个年代的不同选取适当的收集算法,新生代中每次垃圾收集的时候都有大批对象死去,很少数存活,所以使用复制算法,只需要付出少量存活对象的复制成本就能完成收集,老年代中因为对象存活率高,没有额外的空间对他进行分配担保,就使用标记–清理, 标记— 整理

猜你喜欢

转载自blog.csdn.net/qq_39148187/article/details/81984792