Java垃圾回收与对象创建,内存分配

Java垃圾回收与对象创建,内存分配_第1张图片
垃圾回收.png

垃圾回收主要针对Java堆和方法区进行
如何判断对象是否存活,需要回收?

  • 引用计数法
    难以解决对象之间循环引用的问题
  • 可达性分析算法
    思路:通过GC Roots的对象作为起始点,从该节点向下搜索,当一个对象到GC Roots之间没有任何引用链相连(即两者之间不可达时),证明该对象是不可用的。
    有哪些对象可作为GC Roots?见导图

Java引用分类

  • 强引用:该类对象永远不会被回收;如Object obj = new Object();
  • 软引用:有用但并非必须;系统发生内存溢出之前,会将该类对象进行回收,若回收之后还是没有足够内存,则会抛异常
  • 弱引用:只能生存到下一次垃圾回收之前,也就是说,当垃圾收集发生时,都会回收该类对象
  • 虚引用:为一个对象设置该类引用的唯一目的,就是可以在该对象被回收时,收到一个系统通知

回收过程
一个对象,需要经过两次标记,才可能会被回收

  1. 发现其不可达,会被标记一次,并进行一次筛选,条件是该对象是否有必要执行finalize方法
  2. 若有必要执行,则该对象会被放入一个队列中,然后其finalize会被执行
  3. gc会对队列中的对象进行第二次标记,如果对象在finalize方法中将自身与引用链上的任何一个对象建立关联,则该对象会被移出即将回收的集合;但是若其并没有实现上述关联,则会被回收掉
    注意:任何一个对象的finalize方法,都只会被系统自动调用一次!
    该方法不确定性较大,应该尽量避免使用

方法区的回收
永久代垃圾回收主要包括两部分:废弃常量和无用的类
判断是否是无用的类:
必须同时满足下面3个条件

  • 该类所有实例都已经被回收
  • 加载该类的classLoader以被回收
  • 该类的class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
    才可以被回收,但并不是一定就会被回收

垃圾收集算法

  1. 标记-清除算法
  2. 复制算法
    原始复制算法是将内存对半分,根据新生代对象的特点,将内存分为一块较大的edon空间和两块较小的survivor空间,每次使用一块edon和一块survivor,回收时,将上述两者中还存活的对象复制到另一块survivor中,然后再执行清理操作,hotspot虚拟机上述比例:8:1:1
    当剩余的一块survivor内存不够用时,需要依赖老年代进行分配担保,从而将剩余的存活对象直接进入老年代中
    缺点:对象存活率高时,复制操作较多,效率变低,并且需要分配担保,老年代无法直接采用该方法
  3. 标记-整理算法
    标记之后,让所有存活的对象都向一端移动,然后清理到另一端的内存,适用于老年代
  4. 分代收集

hotspot算法实现

  1. gc时,需要对可达性分析,此时,这个工作必须能够在一个确保一致性的环境中进行,因此,gc时需要停顿所有的Java线程。在系统停顿后,可以通过一个OopMap的数据结构,知道引用位置等信息。
  2. 安全点
    程序在执行至安全点时,才可以停顿下来开始gc
    如何在gc发生时,让所有线程都执行至最近的安全点才停顿呢?有以下两种方法:
  • 抢先式中断(几乎不用)
  • 主动式中断:gc发生时,设置一个标志位,各个线程执行时,主动去查询该标志,发现中断为true,则自动挂起;一个重要前提是,该标志位和安全点时重合的
    采用安全点时的问题:当程序没有抢占到CPU时间时,这个时候线程是sleep或者blocked状态的,无法响应JVM的中断请求,到安全点去中断挂起
  1. 安全区域
    指当进入一段代码片段之后,引用关系不会再发生变化,再该区域中任意地方开始gc都是安全的
    逻辑:线程进入安全区时,标识自己已经进入,则发起gc时,就不用再管这种状态的线程了;当线程要离开安全区时,要检查系统是否已经完成了根节点的枚举(或整个gc过程),若已经完成,那线程就继续执行,否则,就等到接收到可以离开安全区的信号为止

垃圾收集器
见导图


内存分配

Java垃圾回收与对象创建,内存分配_第2张图片
内存分配.png

对象的内存分配,就是指在堆上分配,对象主要分配在新生代的edon分区上,如果启动了TLAB,则优先分配其上。
以下是几条最普遍的内存分配规则:

  1. 对象优先在edon分配
    当edon区没有足够的空间进行分配时,虚拟机会发起一次Minor GC
  2. 大对象直接进入老年代
    大对象是指需要大量连续内存空间的Java对象,例如很长的字符串和数组
  3. 长期存活的对象进入老年代
    如何判断长期存活呢?
    虚拟机给每一个对象定义了一个对象年龄计数器,如果对象出生在edon区,并且经过第一次minor gc后依然存活,并且能够被survivor容纳,将被移动至survivor中;并且对象年龄设置为1,对象在survivor分区中每经过一次minor gc,年龄就+1,当其年龄到一定程度(默认为15时),就会晋升至老年代。
  4. 空间分配担保
    发生minor gc之前,会先检查老年代最大可用连续空间是否大于新生代所有对象的总空间,如果大于,则minor gc是安全的;
    否则,虚拟机会查看HandlePromotionFailure值是否允许担保失败,若允许,则会检查上述最大值是否大于历次晋升至老年代对象的平均大小,若大于,则会冒险进行一次minor gc,若小于或者上面的Boolean值为不允许,则改为进行一次full gc
    注意,在jkd 6之后,HandlePromotionFailure参数已经没有作用了,也就是说,上述过程已无需对该参数进行判断。

你可能感兴趣的:(Java垃圾回收与对象创建,内存分配)