JVM--垃圾回收机制与垃圾收集器

finalize方法

  • finalize方法的作用:
    Java技术使用finalize()方法在垃圾回收器在进行垃圾回收之前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用的时候调用的。他是Object类中的定义。finalize方法是垃圾收集器在删除这个对象之前,由这个对象进行调用的。

  • 几点注意事项
    (1)finalize()方法的作用是GC回收垃圾之前对其进行调用(但不是每次都会调用)
    (2)System.gc()的作用是手动回收垃圾。调用该方法的时候并不是百分之百调用finalize方法。只有将不用的对象置为空才会有很大的几率调用该方法。
    如:

Test001 test = new Test001();
test = null;
System.gc();

此时才会有很大的几率调用finalize方法,但还是不是百分之百。当一个对象不可达,没有用的时候,也不是百分之百会调用该方法(finalize)的。

内存溢出和内存泄漏的区别

  • 内存溢出
    假设项目需要4G内存,但是只是支持3个G,则这种情况就是内存溢出。就是说现在需要的内存太多本地无法提供。
  • 内存泄漏
    举个例子就是项目定义了过多静态变量。占用了很多内存,虽然没有使用,但是无法回收

内存泄漏就是对象已经没有被应用程序使用,但是垃圾回收器无法移除他们,因为还在被引用着。

  • 如何防止内存泄露:
    (1)特别注意一些像HashMap和ArrayList的集合对象,他们经常会引发内存泄露。当他们被声明为static时,他们的声明周期和应用程序一样长。
    (2)特别注意事件监听和回调函数。当一个监听器在使用的时候被注册,但不再使用之后却被反注册。
    (3)如果一个类自己管理内存,那开发人员就得小心内存泄露问题,通常一些成员变量引用其他对象,初始化的时候需要置空。

如何判定对象是垃圾对象?

  • 引用计数法
    (1)在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用计数器的值就+1,当引用失效的时候,计数器的值就-1。
    (2)实现简单,效率高,但是基本没人使用,因为会出现当两个对象相互引用的时候并且没有其他的引用的时候,这两个对象应该是垃圾对象,但是计数器依然有值,不能回收。
  • 可达性分析法
    较为常用的垃圾回收算法,通常会定义一个垃圾回收的根节点,从这些节点开始向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链的时候,就证明对象是不可用的。
    在这里插入图片描述
  • 在Java中,可以作为GC Roots的对象包括以下几种:
    (1)虚拟机栈(栈帧中的本地变量表)中引用的对象
    (2)方法区中类静态属性引用的对象
    (3)方法区中常量引用的对象
    (4)本地方法栈中的JNI(一般所说的native方法)引用的对象。

内存分配的规则

  • 内存分配的原则:
    (1)优先分配到eden(eden->survivor->survivor(有两块survivor)->老年代)
    (2)如果有大对象,直接分配到老年代(大对象指的是需要大量连续内存空间的Java对象)
    (3)长期存活的对象分配到老年代
    (4)空间分配担保(就是新生代内存不够,需要向老年代借用内存进行分配)
    (5)动态年龄判断(若存活的对象存活一定的时间,则可以进入老年代;其实只要Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代)

  • 对象优先在Eden分配
    大多数情况下,对象在新生代Eden区分配。当Eden区没有足够的空间进行分配的时候,虚拟机将进行一次Minor GC

  • 大对象直接进入老年代
    (1)所谓的大对象就是指大量连续内存空间的Java对象,最典型的大对象就是很长的字符串以及数组
    (2)经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的空间来安置这些大对象。

  • 长期存活的对象进入老年代
    虚拟机给每个对象定义了一个对象年龄(分代年龄),如果对象在Eden处并经过一次Minor GC后仍然存活,并能被Survivor容纳的话,将被移动到Survivor空间中,并且设置对象的年龄是1。对象在两个Survivor中每次处理后都会增加1岁,当他的年龄达到15岁的时候,就会晋升到老年代。

  • 动态年龄判断
    如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象直接进入老年代,没有必要直接等到15岁再进入老年代。

  • 空间分配担保
    (1)在发生Minor GC之前,虚拟机会先检查老年代最大的连续空间是否大于新生代所有对象的总和,如果条件成立 ,那么Minor GC可以确保是安全的,如果不成立,则JVM会查看是否设置允许担保失败。如果允许,那么会继续检查老年代最大连续可用空间是否大于历次晋升到老年代对象的平均大小。如果大于,将进行一次Minor GC,尽管这次是有风险的。如果小于或者担保失败,那这个时候改为进行一次Full GC(即整个堆都GC,Minor GC + Major GC),并且如果出现大量对象在MinorGC中存活的对象过多时,那么容纳不下的对象将直接进入老年代。

-XX: -HandlePromotionFailure 关闭空间内存担保
-XX:+HandlePromotionFailure 开启空间内存担保

垃圾回收算法

标记清除算法
  • 是一个老年代算法
  • 可以当做是其他垃圾回收算法的基础,比较简单,其实是有两个过程的,一个是标记,一个是清除,标记就是标记需要回收的对象,通过可达性分析法标出没有使用的对象,之后再对没有使用的对象进行清除。
  • 该算法存在部分问题:
    (1)效率问题:
    执行的性能不是很高
    (2)空间问题:
    标记清除之后,会产生大量的不连续的内存碎片,空间碎片太多会导致以后在程序运行过程中需要分配较大的对象时候,无法找到连续的内存而进行一次垃圾收集。
    在这里插入图片描述
标记整理算法
  • 是一个老年代的算法,存活率高,回收较少
  • 是为了解决标记清除算法中产生大量不连续的内存。
  • 先对其利用可达性分析法进行标记,标记出无用的对象,再进行整理,将没有被标记的存活的对象向一端进行移动,然后直接清除掉边界以外的内存。
    在这里插入图片描述
复制算法
  • 主要针对新生代的垃圾回收算法,活着的较少,需要回收的内存多。
  • 堆是垃圾回收经常光顾的区域,堆可以分为新生代和老年代,其中垃圾回收经常关注新生代,老年代不经常关注。
堆:
	新生代
		Eden(创建新生代的时候对象放到该区域)
		Survivor 存活区
		Tenured Gen
	老年代
  • 复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存使用完成,就将还存活着的对象复制到另一块上,再把已经使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配的时候也不用考虑内存碎片等复杂情况,只需要移动堆栈指针,按顺序分配,简单高效。
    在这里插入图片描述
    我们为了避免浪费,又将新生代分为了:
    在这里插入图片描述

重要

将内存分为一个较大的Eden区域和两个较小的Survivor空间。每次使用Eden和一个Survivor空间,当回收的时候,将Eden和Survivor中还存活的对象一次性的复制到另外一个Survivor上,最后清理掉Eden和用过的Survivor。如果另外一块的Survivor空间不够存放上一次Eden和Survivor收集下来的存活的对象,这些对象将通过分配担保机制直接进入老年代。

分代收集算法

采用复制算法+标记整理算法,内存分为新生代和老年代,针对不同的新生代和老年代,采取不同的算法,新生代采用复制算法,老年代采用标记整理算法。

垃圾收集器

请参考博客:
https://blog.csdn.net/qian520ao/article/details/79050982
很详细,不可能看不懂

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/89342122