JVM内容学习-运行时数据区域和垃圾回收

36 阅读12分钟

一,运行时数据区-总览

  • java虚拟机在运行java程序过程中管理的内存区域,称之为运行时数据区。

image.png

二,运行时数据区-程序计数器

  • 程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过程序计数器记录当前要执行的的字节码指令的地址。
  • 作用:
    • 可以控制程序指令的执行,实现分支、跳转、异常等逻辑。
    • 在多线程执行情况下,Java虚拟机需要通过程序计数器记录CPU切换前解释执行到那一句指令并继续解释运行。
    • 因为每个线程只存储一个固定长度的内存地址,程序计数器是不会发生内存溢出的。

(补充:内存溢出:指的是程序在使用某一块内存区域时,存放的数据需要占用的内存大小超过了虚拟机能提供的内存上限。)

二,运行时数据区-栈

  • Java虚拟机栈(Java Virtual Machine Stack)采用栈的数据结构来管理方法调用中的基本数据,先进后出(First In Last Out),每一个方法的调用使用一个栈帧(Stack Frame)来保存。
    • 栈帧的组成
      • 局部变量表:保存在运行过程中存放所有的局部变量
      • 操作数栈:指令执行过程中用来存放临时数据的一块区域
      • 帧数据 :包含动态连接,方法出口,异常表的引用
  • 栈内存溢出:Java虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大大小就会出现内存溢出。Java虚拟机栈内存溢出时会出现StackOverflowError的错误。JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构有关。
    • 要修改Java虚拟机栈的大小,可以使用虚拟机参数 -Xss
    • 局部变量过多、操作数栈深度过大也会影响栈内存的大小

二,运行时数据区-堆

  • 创建出来的对象都存在于堆上
  • used指的是当前已使用的堆内存,total是java虚拟机已经分配的可用堆内存,max是java虚拟机可以分配的最大堆内存。 image.png
  • 随着堆中的对象增多,当total可以使用的内存即将不足时,java虚拟机会继续分配内存给堆,total最多只能与max相等。
  • 堆 – 设置大小
    • 要修改堆的大小,可以使用虚拟机参数 –Xmx(max最大值)和-Xms (初始的total)。

二,运行时数据区-方法区

组成:
  • 类的元信息:方法区是用来存储每个类的基本信息(元信息),一般称之为InstanceKlass对象。在类的加载阶段完成。
  • 运行时常量池:字节码文件中通过编号查表的方式找到常量,这种常量池称为静态常量池。当常量池加载到内存中之后,可以通过内存地址快速的定位到常量池中的内容,这种常量池称为运行时常量池
  • 字符串常量池

image.png

方法区设计(Hotspot):
  • JDK7及之前的版本将方法区存放在堆区域中的永久代空间,堆的大小由虚拟机参数来控制。堆的大小由虚拟机参数-XX:MaxPermSize=值来控制。
  • JDK8及之后的版本将方法区存放在元空间中,元空间位于操作系统维护的直接内存中,默认情况下只要不 超过操作系统承受的上限,可以一直分配。可以使用-XX:MaxMetaspaceSize=值将元空间最大大小进行限制。

image.png

使用直接内存为了解决以下两个问题:

  • Java堆中的对象如果不再使用要回收,回收时会影响对象的创建和使用。
  • IO操作比如读文件,需要先把文件读入直接内存(缓冲区)再把数据复制到Java堆中。现在直接放入直接内存即可,同时Java堆上维护直接内存的引用,减少了数据复制的开销。 (直接内存:不属于Java运行时的内存区域)
    • 如果需要手动调整直接内存的大小,可以使用-XX:MaxDirectMemorySize=大小

自动垃圾回收

  • 内存泄漏:内存泄漏指的是不再使用的对象在系统中未被回收,内存泄漏的积累可能会导致内存溢出。
  • Java中为了简化对象的释放,引入了自动的垃圾回收(Garbage Collection简称GC)机制。通过垃 圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要负责对上的内存进行回收。
  • 自动垃圾回收对比:
    • 自动回收:
      • 优点:降低程序员实现难度,降低对象回收bug的可能性
      • 缺点:程序员无法控制内存回收的及时性
    • 手动回收:
      • 优点:回收及时性高,由程序员把控回收的时机
      • 缺点:编写不当容易出现空指针,重复释放,内存泄漏等问题

方法区的回收

  • 满足以下三个条件:
    • 此类所有实例对象都已经被回收,在堆中不存在任何该类的实例对象以及子类对象。
    • 加载该类的类加载器已经被回收。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用

堆回收

  • Java中的对象是否能被回收,是根据对象是否被引用来决定的。如果对象被引用了,说明该对象还在使用,不允许被回收。
  • 如何判断堆上的对象没有被引用?
    • 引用计数法
      • 引用计数法会为每个对象维护一个引用计数器,当对象被引用时加1,取消减1。
      • 缺点:
        • 每次引用和取消引用都需要维护计数器,对系统性能会有一定的影响。
        • 存在循环引用问题,所谓循环引用就是A引用B,B也同时引用A时出现对象无法回收的问题。
    • 可达性分析法
      • 将对象分为两类:根对象(GC Root)和普通对象
      • 图中A到B再到C和D,形成了一个引用链,可达性分析算法指的是如果从某个到GC Root对象是可达的,对象就不可被回收。 image.png
      • 哪些可以被称为GC Root对象?
        • 线程Thread对象,引用线程栈帧中的方法参数,局部变量等
        • 系统类加载器加载的java.lang.Class对象,引用类中的静态变量。
        • 监视器对象,用来保存同步锁synchronized关键字持有的对象,
        • 本地方法调用时使用的全局对象
  • 几种常见的对象引用?
    • 强引用:通过GC Root引用链能够到达的对象
    • 软引用:软引用相对于强引用是一种比较弱的引用关系,如果一个对象只有软引用关联到它,当程序内存不足时,就会将软引用中的数据进行回收。
    • 弱引用:弱引用的整体机制和软引用基本一致,区别在于弱引用包含的对象在垃圾回收时,不管内存够不够都会直接被回收。
    • 虚引用:不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知。
    • 终结器引用:终结器引用指的是在对象需要被回收时,终结器引用会关联对象并放置在Finalizer类中的引用队列中,在稍后由一条由FinalizerThread线程从队列中获取对象,然后执行对象的finalize方法,在对象第二次被回收时,该对象才真正的被回收。
  • 垃圾回收算法
    • 标记-清除算法
      • 核心思想:
        • 1.标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
        • 2.清除阶段,从内存中删除没有被标记也就是非存活对象。
      • 优缺点:
        • 优点:实现简单,只需要在第一阶段给每个对象维护标志位,第二阶段删除对象即可。
        • 缺点:1.碎片化问题。2.分配速度慢
    • 复制算法
      • 核心思想:
          1. 准备两块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(from空间)
          1. 在垃圾回收GC阶段,将From中存活对象复制到To空间。
          1. 将两块空间的From和To名字互换。
      • 优缺点:
        • 优点:吞吐量高,不会发生碎片化
        • 缺点:内存使用率低
    • 标记整理算法
      • 核心思想:
        • 1.标记阶段,将所有存活的对象进行标记。Java中使用可达性分析算法,从GC Root开始通过引用链遍历出所有存活对象。
        • 2.整理阶段,将存活对象移动到堆的一端。清理掉存活对象的内存空间。
      • 优缺点:
        • 优点:内存使用率高,不会发生碎片化
        • 缺点: 整理阶段的效率不高
    • 分代GC
      • 区域划分 image.png
      • 调整堆内存大小
        image.png
      • 核心思想
        • 分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。Minor GC会把需要eden中和From需要回收的对象回收,把没有回收的对象放入To区,接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生Minor GC。此时会回收eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0。每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1。如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。 当老年代中空间不足,无法放入新的对象时,先尝试minor gc如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。
  • 垃圾回收算法评价标准
    • 吞吐量: 吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值,即吞吐量 = 执行用户代码时间 /(执行用户代码时间 + GC时间)。吞吐量数值越高,垃圾回收的效率就越高。
    • 最大暂停时间:最大暂停时间指的是所有在垃圾回收过程中的STW时间最大值。最大暂停时间越短,用户使用系统时受到的影响就越短。
    • 堆使用效率:不同垃圾回收算法,对堆内存的使用方式是不同的。比如标记清除算法,可以使用完整的堆内存。而复制算法会将堆内存一分为二,每次只能使用一半内存。从堆使用效率上来说,标记清除算法要优于复制算法。
    ( STW:Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。如果STW时间过长则会影响用户的使用。)
为什么分代GC算法要把堆分成年轻代和老年代?
  • 1、可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能。
  • 2、新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法,老年代可以选择标记-清除和标记-整理算法,由程序员来选择灵活度较高。
  • 3、分代的设计中允许只回收新生代(minor gc),如果能满足对象分配的要求就不需要对整个堆进行回收(fullgc),STW时间就会减少。
垃圾回收器
  • 组合使用:

image.png

  • 年轻代-Serial垃圾回收器:

image.png

  • 老年代-SerialOld垃圾回收器:

image.png

  • 年轻代-ParNew垃圾回收器:

image.png

  • 老年代- CMS(Concurrent Mark Sweep)垃圾回收器:

image.png

image.png

image.png

  • 年轻代-Parallel Scavenge垃圾回收器

image.png

  • 老年代-Parallel Old垃圾回收器

image.png

  • G1垃圾回收器

    • JDK9之后默认的垃圾回收器是G1(Garbage First)垃圾回收器
    • 优点:
      • 1.支持巨大的堆空间回收,并有较高的吞吐量。
      • 2.支持多CPU并行垃圾回收。
      • 3.允许用户设置最大暂停时间
    • 内存结构 : G1的整个堆会被划分成多个大小相等的区域,称之为区Region,区域不要求是连续的。分为Eden、Survivor、Old区。Region的大小通过堆空间大小/2048计算得到,也可以通过参数-XX:G1HeapRegionSize=32m指定(其中32m指定region大小为32M),Region size必须是2的指数幂,取值范围从1M到32M。
    • G1垃圾回收有两种方式:
      • 1、年轻代回收(Young GC):年轻代回收(Young GC),回收Eden区和Survivor区中不用的对象。会导致STW,G1中可以通过参数-XX:MaxGCPauseMillis=n(默认200) 设置每次垃圾回收时的最大暂停时间毫秒数,G1垃圾回收器会尽可能地保证暂停时间。

      • 2、混合回收(Mixed GC):多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时 (-XX:InitiatingHeapOccupancyPercent默认45%)会触发混合回收MixedGC。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成

image.png

(注意:如果清理过程中发现没有足够的空Region存放转移的对象,会出现Full GC。单线程执行标记-整理算法,此时会导致用户线程的暂停。所以尽量保证应该用的堆内存有一定多余的空间)

image.png