GC垃圾回收

垃圾收集算法的核心思想

答:Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。
  垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。

触发主GC(Garbage Collector)的条件
答:①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。
  ②Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。

减少GC开销的措施
答:
(1)不要显式调用System.gc()
(2)尽量减少临时对象的使用
(3)对象不用时最好显式置为Null
(4)尽量使用StringBuffer,而不用String来累加字符串
(5)能用基本类型如Int,Long,就不用Integer,Long对象
(6)尽量少用静态对象变量
(7)分散对象创建或删除的时间

gc与finalize方法
答:finalize(),其工作原理:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以如果用finalize()就能在垃圾回收时刻做一些重要的清理工作。
gc 只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存(当使用JNI技术时,可能会在栈上分配内 存,例如java调用c程序,而该c程序使用malloc分配内存时).因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行 内存回收就要靠finalize().

方法区/永久代(线程共享)
即我们常说的永久代(Permanent Generation),用于存储被 JVM 加载的类信息、常量、静 态变量、 即时编译器编译后的代码等数据.HotSpot VM 把 GC 分代收集扩展至方法区,即使用 Java 堆的永久代来实现 方法区,这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门 的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载,因此收益一般很小)。 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字 段、方法、接口等描述等信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加 载后存放 当前线程 Stack Frame 2 栈帧 1 Stack Frame 1 当前栈帧 Currentstack Frame到方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自然也包括常量 池)的格式都有严格 的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机认可、装载和执行。

JVM 运行时内存 Java 堆从 GC 的角度还可以细分为:新生代区、From Survivor 区和 To Survivor 区)和老年 代。
在这里插入图片描述

2.3.1.新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

2.3.1.1. Eden 区

Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。 当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。

2.3.1.2. ServivorFrom

上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

2.3.1.3. ServivorTo

保留了一次 MinorGC 过程中的幸存者。

2.3.1.4. MinorGC 的过程(复制->清空->互换)

MinorGC 采用复制算法

1: eden、servicorFrom 复制到 ServicorTo,年龄+1 首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年 龄以及 达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1 (如果 ServicorTo 不 够位置了就放 到老年区); 2:清空 eden、servicorFrom 然后,清空 Eden 和 ServicorFrom 中的对象;
3: ServicorTo 和 ServicorFrom 互换 最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom 区。

2.3.2. 老年代 主要存放应用程序中生命周期长的内存对象。 老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行 了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间 分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。 MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的 对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减 少内存损耗,我们 一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of Memory)异常。2.3.3. 永久代 指内存的永久保存区域,主要存放 Class 和 Meta (元数据)的信息,Class 在被加载的时候被 放入永 久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久 代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

2.3.3.1. JAVA8 与元数据

在 Java8 中,永久代已经被移除,被一个称为"元数据区"(元空间)的区域所取代。元空间 的本质和 永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用 本地内存。因此, 默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native memory,字符串池和类的静态变量 放入 java 堆中,这样可以加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控 制。

2.4.垃圾回收与算法

2.4.1.如何确定垃圾

2.4.1.1. 引用计数法

在 Java 中,弓 I 用和对象是有关联的。如果要操作对象则必须用弓 I 用进行。因此,很显然一个简单 的 办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关 联的引用, 即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象

2.4.1.2. 可达性分析

为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的"GC roots” 对象作 为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记 过程。两 次标记后仍然是可回收对象,则将面临回收。

2.4.2.标记清除算法(Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清 除阶段回 收被标记的对象所占用的空间。如图
在这里插入图片描述

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可 利用空间 的问题。

2.4.3.复制算法(copying)

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小 的两块。 每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,如图:
13/04/2018 Page 28 of 283
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原 本的一半。 且存活对象增多的话,Copying 算法的效率会大大降低。

2.4.4.标记整理算法(Mark-Compact)

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清 理对象, 而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:
在这里插入图片描述

2.4.5.分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存 划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生 代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃 圾回收时者隋大量垃圾需要 被回收,因此可以根据不同区域选择不同的算法。

2.4.5.1.新生代与复制算法

目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要 回收大部 分对象,即要复制的操作比较少,但通常并不是按照 1: 1 来划分新生代。一般将新生代 划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用 Eden 空间和 其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另 —块 Survivor 空间中。 新生代 Eden Space From Space To Space Eden 区 SurvivorE

在这里插入图片描述

2.45.2.老年代与标记复制算法

而老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。

  1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类, 常量,方 法描述等。对永生代的回收主要包括废弃常量和无用的类。

  2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目 前存放对 象的那一块),少数情况会直接分配到老生代。

  3. 当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行 GC后,Eden Space和From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From Space 进行清理。

  4. 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。

  5. 在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。

  6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被 移到老生 代中

GC 分代收集算法 VS 分区收集算法

2.6.1.分代收集算法

当前主流 VM 垃圾收集都采用"分代收集-(Generational Collection)算法,这种算法会根据 对象存活 周期的不同将内存划分为几块,如 JVM 中的新生代、老年代、永久代,这样就可以根据 各年代特点分别采 用最适当的 GC 算法

2.6.1.1. 在新生代-复制算法

每次垃圾收集都能发现大批对象已死,只有少量存活.因此选用复制算法,只需要付出少量 存活 对象的复制成本就可以完成收集.

2.6.1.2. 在老年代-标记整理算法

因为对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记一清理"或"标 记一整理 "算法来进行回收,不必进行内存复制,且直接腾出空闲内存.

2.6.2.分区收集算法

分区算法则将整个堆空间划分为连续的不同小区间,每个小区间独立使用,独立回收.这样做的 好处 是可以控制一次回收多少个小区间,根据目标停顿时间,每次合理地回收若干个小区间(而不是 整个堆), 从而减少一次 GC 所产生的停顿。

2.7. GC 垃圾收集器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法; 年老代 主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不 同的垃圾收集器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值