JVM垃圾回收算法与内存分析

在内存管理与内存溢出中我们说过JAVA和C/C++的区别是内存的管理方式,还有一种方式就是本文介绍的垃圾收集技术

引用一句深入理解JAVA虚拟机的一句话就是

JAVA和C/C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人项进去,墙里面的人想出来

学习垃圾回收器的原因:

随着内存动态分配技术和垃圾回收技术的发展,如今这些动作仿佛已经进入了自动化的阶段,好像一个黑盒子,我们仿佛不需要了解它,就可以写出代码。但是如果出现错误的话,我们不了解它的原理,我们在无法排查各种内存溢出,内存泄漏的问题,我们无法突破在垃圾收集达到系统最高并发量的瓶颈。

垃圾回收器主要回收的区域:JAVA堆和方法区

一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有在运行期间,我们才能知道程序究竟创建哪些对象,创建多少个对象,这部分内存分配和回收是动态的。垃圾回收器所关注的就是这部分内存如何管理。

垃圾回收的前提:确保对象已死,如何判断对象已死

  1. 引用计数法:普遍的人们认为 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时,计数器就减一;任何时刻计数器为零的对象是不可以被引用的.

    弊端:对于循环引用时那么就无法进行GC,而且java虚拟机也并不是通过引用计数法来判断对象是否存活的。

  2. 可达性分析法:目前商用的程序设计语言都是通过可达性分析判断对象是否存活的。算法 的基本思路就是通过一系列称为GC Roots的根对象作为起始结点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径成为引用链,如果某个对象到GC Roots间没有任何引用链相连,证明此对象不可能再被使用

    扫描二维码关注公众号,回复: 11851936 查看本文章

在这里插入图片描述
关于GC Roots

在JAVA的体系中,固定可作为GC Roots的对象的是

  1. 在虚拟机栈中引用的对象。比如各个线程被调用的方法堆中使用到的参数,局部变量,临时变量等
  2. 在方法区中类静态属性引用的对象,比如JAVA类中的引用类型静态变量。
  3. 在方法区中常量引用的对象,比如字符串常量池里的引用。
  4. 在本地方法中JNI的引用(java的native方法)

注意:一个对象的finalize()只能被执行一遍。

  1. 回收方法区:

    ​ 注意:方法区的回收不像堆区的回收,因为堆区存储的是实例的对象,比较容易判断是否可以回收,但是方法区存储的是类型信息以及一些常量等。

    要回收方法区需要满足三个条件

    1. 该类的所有实例都已经被回收,也就是java堆里面没有存在任何派生子类的实例;

    2. 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景;

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

      满足以上条件才允许对无用的类进行回收,注意是被允许。

  2. 标记-复制算法

    1. 面对大量可回收对象时执行效率低的问题,产生了半区复制的方式。这种方式就是survivor区的设置模式,survivor分为了from区和to区,这两个区所有的对象只能同时存在一个区,不允许存在两个区中,
    2. 主要工作流程就是:在eden经理过垃圾回收后,将生存力强的对象放到一个survivor区域中,当再次进行垃圾回收的时候将存活的对象复制到另一个区域中。
  3. 标记-清除算法

    1. 对于需要回收的对象进行标记处理 ,标记完成之后会统计回收掉所有标记的对象。

      弊端就是:执行效率不稳定,如果Java堆里面存在大量的对象,并且大部分是要回收的,这时就要进行大量的标记和清除的动作,导致标记和清除的两个过程中执行效率都随对象数量的增长而降低;

      内存碎片化的问题: 当增加一个比较大的对象时,空间由于不存在较大的连续的空间进行存储,就会导致提前进行垃圾回收动作

  4. 标记-整理算法

    1. 为了解决内存碎片化的问题,标记整理算法将存活的对象向内存空间的一段移动,然后直接清除掉边界以外的内存;
    2. 弊端会触发stop the world的动作,在整理的过程中必须停掉用户的线程,以便移动的过程中,中间存在分配对象的情况。

内存分配与回收策略

  1. 对象优先分配在Eden区,当eden中没有足够的空间进行内存分配时,虚拟机会发起一次Minor GC ,我们可以通过虚拟机参数-XX:+PrintGCDetails这个收集器日志参数 告诉虚拟机在垃圾收集行为时打印内存回收日

    志,

  2. 大对象直接进入老年代

    1. 虚拟机参数:-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接进入老年代分配,这样做的目的是避免在Eden区和两个Survivor中来回复制,产生大量的内存复制操作。
  3. 长期存活的对象进入老年代

    1. 对象在eden中经历过第一次MinorGC之后仍然存活会被移动到Survivor中,并且将对象的年龄设置为1,并且在Survivor区中每熬过一次Minor GC 年龄就增加1岁,当年龄到达一定程度时,就会被晋升到老年代,对象老年代的阀值可以通过参数-XX:MaxTenuringThreshold来设置最大的年龄,默认的最大年龄为15
  4. 动态对象年龄判定

    1. 为了更好的适用不同程序的内存情况,HotSpot虚拟机并不是永远要求对象的年龄必须达到-XX:MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的对象总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代。
  5. 空间内存担保

    1. 在发生MinorGC之前,虚拟机必须检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,哪这一次MinorGC可以确保时安全的,如果老年代的最大连续空间不大于新生代所有对象的总空间,查看-XX:HandlePromotionFailure参数的设置,是否允许担保失败,如果允许,当连续空间大于新生代的总空间时,将尝试一次Minor GC 如过小于就进行Full GC

猜你喜欢

转载自blog.csdn.net/qq_43079376/article/details/105816153