转载

java垃圾收集算法

基础背景

运行时数据区域

虚拟机结构图

java垃圾收集算法

  • 程序计数器:

每个线程独有一份,用作记录编译后的class文件行号

  • 虚拟机栈:以栈帧为单位存放局部变量.
  • Native方法栈:和虚拟机栈类似,不过,一个本地方法是这样一个方法:该方法的实现由非java语言实现,比如C语言实现。很多其它的编程语言都有这一机制,比如在C++中,你可以告知C++编译器去调用一个C语言编写的方法
  • 方法区:运行时常量池,存放编译器的字面量和符号引用,也可以在运行时动态加入.
  • java堆:存放对象的实例,是垃圾回收的主战场,

创建一个对象

  1. 比如执行 new MyClass();
  2. 去常量池中寻找,查看类是否被加载.如果没加载,则加载class.
  3. 在java堆中分配内存空间,方式有以下两种:
  • 指针碰撞:把指针向空闲对象移动与对象占用内存大小相等的距离,使用的收集器有Serial、ParNes等
  • 空闲列表:虚拟机维护一个列表,记录可用的内存块,分配给对象列表中一块足够大的内存空间,使用的收集器有CMS等.
  • 如何分配内存,由垃圾回收器决定.
  1. 内存的具体分配过程中有同步和预留空白区的方式
  2. 内存分配好后,再执行init()方法,初始化实例.

对象头

  1. 对象头主要记录对象的hashcode,GC标记,元数据地址,以及关于对象锁的使用,年龄代,偏向线程等。
  • hash用于快速寻找对象
  • 对象头大小32bit/64bit,由虚拟机决定
  • 实例数据区的数据类型,按照相似放在一起.

对象中的访问定位

方式

  1. 句柄池
  • 句柄池从堆中划分
  • 由实例地址和类型数据地址构成
  1. 指针
  • 可直接通过指针访问到实例对象

优劣

句柄的使用,方便了实例位置的改变,可以不改变引用,但是访问速度相对于指针低一些.

JVM垃圾回收

判断可否回收的算法

1.引用计数算法:

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不再被使用的,垃圾收集器将回收该对象使用的内存。

  1. 可达性分析算法:

通过一系列的名为GC Root的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。

实例的位置:

  1. java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
  2. 方法区中的类静态属性引用的对象。
  3. 方法区中的常量引用的对象。
  4. 本地方法栈中JNI本地方法的引用对象。
  • 不可达对象到死亡还需要两次标记,第一次,标记后进入F-Queue队列,第二次标记时只有finalize()中有拯救自己的方法的实例才能自救成功,比如将自己应用给其它变量.

垃圾回收算法

方法区的回收

常量池的回收

  1. 没有被引用,即可被回收

class对象回收

  1. 所有实例都被回收
  2. 所有classLoader都被回收
  3. java.lang.class对象没有被任何地方引用,即无法在任何地方使用反射访问类.
  4. 最终是否被回收,还得看JVM参数配置

java堆回收算法

  1. 标记清除算法: 先标记判定,再一次性清除.
  • 产生了大量碎片,且效率低下
  1. 复制算法: 把可用内存划分为两块,一块用完后,就将活下来的实例放到另一块内存区.
  • 优缺点:没有了碎片化问题,但内存大小减少了一半
  1. 标记整理算法: 在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
  • 标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记整理算法效率会大大提高。
  1. 分代收集算法:根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老 代中创建。现在的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法.

    java虚拟机垃圾收集器关注的内存结构如下:

java垃圾收集算法

新生代

研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两部分内存,而是将新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,只有Eden和其中一个Survivor总共90%的新生代容量用于为新创建的对象分配内存,只有10%的Survivor内存浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行 标记-清理 回收,两个Survivor区域是轮换的。

年老代

  • 年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用 标记-整理 垃圾回收算法。
  • Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

堆分配和回收策略

分配

  • 优先在Eden上分配,空间不足,虚拟机发起minor GC.
  • 大对象直接进入老年代,防止折磨新生代空间.[参数设置 -XX:PretrnureSizeThreshold=[字节数] ]
  • 长大后的对象进入老年代,在survivor中熬过一次,就长一岁,15岁时就进入老年代[阈值设置 -XX:MaxTenuringThreshold=[岁数] ]
  • 相同年龄的对象,若大于或等于空间的一半,也直接进入老年代.

附:JVM参数整理

参数调优建议

java垃圾收集算法

  • 永久代: -XX:PermSize20M -XX:MaxPermSize20M
  • 堆大小: -Xms20M -Xmx20M
  • 新生代: -Xmn10M
  • Eden与survior比率: -XX:SurvivorRation=8
  • 让大对象直接进入老年代: -XX:PretrnureSizeThreshold=1B
  • 年龄阈值: -XX:MaxTenuringThreshold=15

日志命令: 参考连接

原文  https://segmentfault.com/a/1190000018519442
正文到此结束
Loading...