Java 垃圾回收机制

垃圾收集GC(Garbage Collection)是Java语言的核心技术之一, 在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。在平时开发中需要创建大量的对象,如果这些对象没有回收那么会占用大量的内存空间,最后会导致溢出。

Java内存回收那么首先要知道jvm的内存结构
jvm运行时内存结构
java运行时内存主要是分为三个部分
其中PC寄存器、java虚拟机栈、本地方法栈3个区域是所有线程独有的一块区域,随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着入栈和出栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束,内存自然就跟随着回收了。
而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收是动态的,垃圾收集关注的是这部分的内存。

一.垃圾回收首先要知道哪些是垃圾
jvm为了区分哪些是垃圾为此有了两种算法来区分垃圾。

  1. 引用计数法:引用计数法就是给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的,可以当做垃圾收集。
    优点:执行效率高
    缺点:无法检测出循环使用的情况,会造成内存泄露。
  2. 可达性分析算法:可达性分析基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。不能到达的则被可回收对象。
    在这里插入图片描述
    GC Roots 本身是一个出发点,也就是说我们每次进行可达性分析的时候都要从这个初始点出发。换句话说,初始点我们一定是可达的。那么,Java 里有哪些对象可以作为GC Roots呢?主要有以下四种:

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

找出垃圾之后就要进行垃圾回收

二. 垃圾回收

  1. 标记清理算法
    第一步(标记),利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。第二步(清理),我们再遍历一遍,把所有“垃圾”对象所占的空间直接 清空 即可。
    特点:
    简单方便
    容易产生内存碎片
  2. 标记整理算法
    第一步(标记):利用可达性遍历内存,把“存活”对象和“垃圾”对象进行标记。
    第二步(整理):把所有存活对象堆到同一个地方,这样就没有内存碎片了。
    特点:
    适合存活对象多,垃圾少的情况
    需要整理的过程
  3. 复制算法
    将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还活着的对象复制到另一块上,然后再把使用过的内存空间一次性清理掉
    特点:
    简单
    不会产生碎片
    内存利用率太低,只用了一半
  4. 分代收集算法
    当前虚拟机的垃圾收集都采用分代收集算法,这种算法就是根据具体的情况选择具体的垃圾回收算法。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
    三. 堆和方法区中的垃圾回收
    java中的垃圾回收大致在两部分,第一个就是堆、第二个就是方法区。为此先看方法区是如何进行垃圾回收的。
    1. 方法区中的垃圾回收:
      方法区又叫做永久代。永久代的垃圾回收主要有两部分:废弃常量和无用的类。
      首先是废弃常量垃圾回收的一般步骤:
      第一步:判定一个常量是否是废弃常量:没有任何一个地方对这个常量进行引用就表示是废弃常量。
      第二步:垃圾回收
      然后是无用的类垃圾回收的一般步骤
      第一步:判定一个类是否是“无用的类”:需要满足下面三个条件
      Java堆中不存在该类的任何实例,也就是该类的所有实例都被回收
      加载该类的ClassLoader已经被回收
      该类对应的Class对象在任何地方没有引用了,也不能通过反射访问该类的方法。
      第二步:满足上面三个条件就可以回收了,但不是强制的。
    2. Java 堆的垃圾回收:分代回收算法
      首先要知道java堆的结构
      jvm堆空间结构Java 堆空间分成了三部分,这三部分用来存储三类数据:
      1.刚刚创建的对象。
      2.存活了一段时间的对象。
      3.永久存在的对象。
      也就是说,常规的 Java 堆至少包括了 新生代 和 老年代 两块内存区域,而且这两块区域有很明显的特征:
      新生代:存活对象少、垃圾多
      老年代:存活对象多、垃圾少
      针对这种特点,我们有如下的几种方案;
      (1)新生代-复制 回收机制
      对于新生代区域,由于每次 GC 都会有大量新对象死去,只有少量存活。因此采用 复制 回收算法,GC 时把少量的存活对象复制过去即可。但是从上面我们可以看到,新生代也划分了三个部分比例:Eden:S1:S2=8:1:1。
      工作原理如下:
      1.首先,Eden对外提供堆内存。当 Eden区快要满了,触发垃圾回收机制,把存活对象放入 Survivor A 区,清空 Eden 区;
      2.Eden区被清空后,继续对外提供堆内存;
      3.当 Eden 区再次被填满,对 Eden区和 Survivor A 区同时进行垃圾回收,把存活对象放入 Survivor B区,同时清空 Eden 区和Survivor A 区;
      4.当某个 Survivor区被填满,把多余对象放到Old 区;
      5.当 Old 区也被填满时,进行 下一阶段的垃圾回收。
      下面看看老年代的垃圾回收
      (2)老年代-标记整理 回收机制
      老年代的特点是:存活对象多、垃圾少。因此,根据老年代的特点,这里仅仅通过少量地移动对象就能清理垃圾,而且不存在内存碎片化。也就是标记整理的回收机制。既然是标记整理算法,而且老年代内部也不存在着内存划分,所以只需要根据标记整理的具体步骤进行垃圾回收就好了。

注意:ava8中取消了永久代
Java8中取消了永久代,但是同时又用云空间来代替了它
云空间是存在与本地内存中,意味着只要本地内存够大,那就就不会出现oom这种错误。它默认是可以无限使用本地内存的。
但是JVM同样提供了参数来限制它使用的使用。

-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。
-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值