GC基本原理学习(Garbage Collected)

来源:互联网 发布:软件测试管理方法 编辑:程序博客网 时间:2024/05/18 01:46

引言

Java的内存动态分配和垃圾收集的问题,都交给了JVM来处理。注意,将JVM运行数据区(虚拟机栈【栈帧】,程序计数器,堆内存)粗略的分为栈和堆(所有线程共享),回收的是堆中的对象实例。不是栈中的引用类型。
绘制的内存图
那么JVM是如何处理的?
从三个问题来分析:
1. 哪些内存需要回收?
2. 什么时候进行回收?
3. 如何回收?

注:现代收集器基本采用分代收集算法,堆分为:新生代和老年代。

1. 哪些内存需要回收?什么时候回收?

1.1 了解下对象的创建:

  1. 通过new 关键字。
  2. JVM遇到new指令,检查是否能在常量池中定位到一个类的符号引用。
  3. 检查是否已被加载,解析,初始化过。
  4. 没有,则执行相应的类加载过程。
  5. 类加载检查通过后,为新生对象分配内存。(类加载后确定对象所需内存大小)
  6. 从Java堆中划分出一块确定的内存。

1.2 若为“死亡”的对象,则需要回收,如何判断对象是否存活?

1.2.1 引用计数算法

  • 给对象添加一个引用计数器
  • 每当有一个地方引用它时,计数器指加1
  • 当引用失效时,计数器指减1
  • 任何时刻计数器为0的对象就是需要回收的
    Java虚拟机没有采用这种方法来管理内存,主要原因是它难以解决对象之间相互循环引用的问题。(python中采用)

1.2.2 可达性分析算法

  • 用一个“GC Roots”的对象(指一系列中的其中一个并非某一种)作为起始点
  • 从该节点向下搜索,搜索走过的路径称为引用链
  • 若一个对象没有与任何引用链相连,即不可达
  • 证明该对象是不可用,死亡
    Java,C#采用此方法。

1.2.3 哪些可作为GC Roots的对象?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

1.2.4 无论哪种算法都是与“引用”有关,下面分析引用的4种类型,强度依次减弱。

  • 强引用:Java中普遍存在的(如:Object obj = new Object()),只要强引用还在,垃圾收集器永远不会回收掉被引用的对象实例。
  • 软引用:用于有用但非必需的对象。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。[SoftReference]
  • 弱引用:用于非必需对象。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。[WeakReference]
  • 虚引用:不影响生存时间,唯一目的就是能在这个对象被收集器回收时收到一个系统通知。[PhantomReference]

1.2.5 详解,真正的”死亡”对象(两次标记后进行回收

事实上,不可达的对象,也并非“非死不可”,这时它们处于“缓刑”阶段,真正死亡至少要经历两次标记过程:

1.2.5.1 第一次标记
  • 对对象进行可达性分析后发现没有与GC Roots相连接的引用链
  • 筛选是否有必要执行finalize()方法[没有覆盖finalize()方法或finalize()方法已经被JVM调用过,视为“无需执行”]
1.2.5.2 第二次标记【需要执行finalize(),通过筛选】
  • 将该对象放置F-Queue的队列中
  • JVM自动建立一个低优先级的Finalizer线程去执行它(会触发执行,但不一定等待它运行结束)
  • GC对F-Queue中的对象进行标记(对象可通过finalize()拯救自己,重新关联引用链等)
    不建议使用finalize()

1.3 上面讲的是需要回收的堆内存,关于回收方法区(或HotSpot虚拟机中的永久代),效率低

1.3.1 废弃常量

  • 与堆类似,没有任何对象和其它地方引用的常量
  • 随内存回收,被系统清理出常量池

1.3.2 无用的类

  • 该类所有的实例都已经被回收(堆中无该类的实例)
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法。

2. 如何回收?【垃圾收集算法】

2.1 标记-清除[Mark-Sweep]算法

  • 标记出所有需要回收的对象
  • 统一回收(清除)所有被标记的对象
    缺点:效率不高;标记清除后会产生大量不连续的内存碎片。

2.2 复制[Copying]算法

  • 将可用内存按容量划分为大小相等的两块
  • 每次只使用其中的一块
  • 当使用的这块内存用完了,则将还存活的对象复制到另一块上面
  • 把使用过的那块内存一次性清理掉
    优点:实现简单,运行高效,可按顺序分配内存。
     缺点:内存直接缩小为原来一半,代价太大;对象存活率较高时,效率变低。

2.3 标记-整理[Mark-Compact]算法

  • 标记出所有需要回收的对象
  • 让所有存活的对象都向一端移动(整理)
  • 清理掉存活对象端以外的内存
    适合堆中的老年代的垃圾收集

2.4 分代收集[Generational Collection]算法(商业虚拟机主要采用方法)

  • 根据对象存活周期的不同将内存划分为几块
  • 一般分为新生代和老年代
  • 不同年代采用最适当的收集算法
  • 新生代,一般复制算法
  • 老年代,一般“标记-清理”或“标记-整理”
    具体如何回收,需依据具体的垃圾收集器实现

关于内存泄露

  1. OutOfMemoryError异常,java堆溢出:-Xms ,-Xmx
    对象不断被创建,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
  2. VM Stack溢出。-Xss
  3. 常量池溢出。-XX:PermSize,-XX:MaxPermSize

参考:《深入理解Java虚拟机》

0 0