JAVA ( 二 ) jvm垃圾回收和回收算法

川长思鸟来 2023-02-25 03:39 73阅读 0赞

前沿

之前讲过jvm的内存,程序计算器,本地方法栈,java虚拟机栈,堆。从下图可以看出,

程序计算器,本地方法栈,java虚拟机栈都是线程隔离的,也就是这些区域是随线程而生,也随线程而灭,

所以不需要做过多的回收考虑,

而对于 Java 堆和方法区,我们只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的正是这部分内存。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NocmVrMTE_size_16_color_FFFFFF_t_70

了解之前,我们先看几个常见的面试问题。

JAVA中的创建出来的对象进入到JVM的哪个区域?

堆大致又可以分为那些区域,分别大概占比多少?

垃圾回收GC分为那几种,分别什么时候触发?常说的OOM内存溢出又是什么?

垃圾回收的算法有那些?怎么设置调优JVM参数?

  1. 首先,JVM的堆大体上分为新生代区和老年代区,堆的大小可以通过参数设置 Xms、-Xmx,新生代和老年代的比例默认是12,新生代区又分为EdenFrom Survivor(后面from代替)、To Survivor(后面to代替),比例是 8:1:1,为什么是8:1:1,这个是有道理的,在后面回收算法的时候讲。
  2. 当我们 new 一个对象的时候,绝大部分的对象都会进入到新生代的Eden区,有些大对象会直接进入到老年代区,当对象越来越多的时候,Eden区的剩余空间达到一个值的时候,会触发Minor GC,会对新生代里的对象进行一次回收,那怎么判断那些对象是否是可以回收的?当然就是那些没有引用的对象咯!那又怎么判断哪些对象是没有被引入的了?

1,引用计数法。这种方法就是在对象头部有个couner的计数器,当对象被引用一次时,改counter加一,当对象引用失效时,改计数器就会减一,这样,当我们进行垃圾回收的时候,只要判断这个对象的count是否等于0时,就可以判断是否进行对象回收。

  1. 引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法。但是主流的 Java 虚拟机里没有选用引用计数算法来管理内存,主要是因为它很难解决对象之间循环引用的问题。循环引用就是 A对象里有个对象引用B,B对象里有个对象引用A,这个导致他们的计数器将永远不为0.

2,可达性分析算法,所有和 GC Roots 直接或间接关联的对象都是有效对象,和 GC Roots 没有关联的对象就是无效对象。

  1. 有哪些GC root勒?
  2. 1Java 虚拟机栈(栈帧中的本地变量表)中引用的对象
  3. 2,本地方法栈中引用的对象
  4. 3,方法区中常量引用的对象
  5. 4,方法区中类静态属性引用的对象

GC Roots 并不包括堆中对象所引用的对象,这样就不会有循环引用的问题。

  1. 通俗的说,就是当我们要回收对象的时候,向上一直去找对象的引用,一直向上找,找到它的第一个祖宗,如果它是上面的GC root中的一个,那么这个对象就可达,就不能回收掉,否则就回收掉。

那我们回收对象的时候又有那么算法勒?

  1. **1,标记-清除法 ** 标记就是遍历所有的GC Roots,可达对象做标记为可存活对象,
  2. 清除就是遍历所有的对象,将未标记的对象回收,同时把上一步的那些做标记的对象的标记清除。
  3. 这种算法就是 效率都不是很高,就算清除后也会产生大量的内存碎片。
  4. **2,复制算法(新生代)** 就是分成两块一样大小的内存,每次分配内存时,只使用一块,当这块区域达到一份阈值时,就会触发动作Minor GC,将内存中的可活对象复制到第二块空置内存中,然后清除第一块内存。
  5. 这样做的优点就是不会有内存碎片,缺点显而易见,会浪费内存空间。
  6. o**k,上面我们说的新生代区又分为Edenfromto,比例是 8:1:1就是用的这种垃圾回收,像我们在新生代区会经常的触发Minor GCEden内存达到设定值会触发,from区达到设定值也会触发,每次触发,Eden的可存活对象会进入from区,对象年龄加一,fromMinor GC的时候,也是一样,可存活对象复制到to区,对象年龄加一(ps,当新生代里对象年龄默认到15的时候,对象进入老年代),from区内存清除。这个时候 之前的to区就会变成存放那些复制对象的from区,之前的form区就变成的空置的to区,来了个对调。**

当新生代进行Minor GC,可存活的对象占新生代超过内存大小的10%,也就是form存放不下的时候,这个时候通过分配担保机制将超过的对象直接分配到老年代区域中

  1. **3,标记-整理算法**(老年代)

标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历 GC Roots,然后将存活的对象标记。

整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

  1. 这是一种老年代的垃圾收集算法。老年代的对象一般寿命比较长,因此每次垃圾回收会有大量对象存活,如果采用复制算法,每次需要复制大量存活的对象,效率很低
  2. 最后当老年代的内存达到一定值的时候,就会触发一次full gc,每次full gc会伴随一次Minor GC,当我们full gc也回收不了对象,释放不了内存的时候,这个时候就会报OOM了。

堆的参数配置

-XX:+PrintGC 每次触发GC的时候打印相关日志

-XX:+UseSerialGC 串行回收

-XX:+PrintGCDetails 更详细的GC日志

-Xms 堆初始值

-Xmx 堆最大可用值

-Xmn 新生代堆最大可用值

-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例

发表评论

表情:
评论列表 (有 0 条评论,73人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JVM垃圾回收算法

    Java是一门不用程序员手动管理内存的语言,全靠JVM自动管理内存,既然是自动管理,那必然有一个垃圾内存的回收机制或者回收算法。 在Java堆上分配一个内存给实例对象时,此时

    相关 JVM 垃圾回收算法

    在说垃圾回收算法之前,先谈谈JVM怎样确定哪些对象是“垃圾”。 1.引用计数器算法: 引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器\+1

    相关 JVM垃圾回收算法

    前言 程序计数器,虚拟机栈,本地方法3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出有条不紊地执行着出栈和入栈操作,每一个栈帧中分配多少内存,基本上是在类结