JVM内存模型和垃圾回收

作者: 可乐游侠 | 来源:发表于2018-09-05 21:57 被阅读12次

    在开发过程中,常接触到内存内存,但内存究竟长什么样呢?

    JVM内存模型

    运行时数据区

    jvm中将内存分为程序计数器、Java虚拟机栈、本地方法栈、堆、方法区五个部分。其中Java虚拟机栈、本地方法栈和程序计数器是每个线程独有的,而堆和方法区是所有线程共享的。下面分别介绍各个内存区域。

    • 程序计数器(PC,Program Counter Register):每个线程都有自己的程序计数器,主要用来记录当前线程正在执行的JVM指令地址。如果当前正在执行的方法是native方法,则是未指定值undefined。由于在多线程下,一个CPU内核只能执行一条线程中的指令,因此在线程切换之后能从原来的指令继续执行。
    • Java虚拟机栈(Java Virtual Machine Stack):Java虚拟机栈中保存的栈帧,对应一个个Java方法调用。当前方法调用了其它方法时,会创建一个新的栈帧,一直到方法执行结束,便会将栈帧移除。栈帧中包括:局部变量表、操作数栈、动态链接、方法返回地址、附加信息。
      1. 局部变量表:主要存储方法中的局部变量和方法的参数。
      2. 操作数栈:作为虚拟机的工作区,程序中借助操作数栈来完成所有的计算过程,大多数指令都从这里弹出数据,执行计算,然后把结果压回操作数栈。有点类似CPU寄存器。
      3. 动态链接:每个栈帧都有一个运行时常量池的引用,这个引用指向栈帧当前运行方法所在类的常量池,用以支持动态链接。在Java类文件编译时,将类的变量和方法的引用当做符号引用存储在这个类的常量池里。符号引用是一个逻辑引用,不直接指向内存地址。在类文件加载并校验通过之后,将符号引用替换成直接引用,称为静态解析;在第一次使用符号引用时替换成直接引用,称为动态链接。
      4. 方法返回地址:方法执行完成之后要返回调用的地址,因此在这里记录方法返回地址信息。
      5. 附加信息:具体虚拟机实现中增加的,虚拟机规范中没有包含的信息。
    • 本地方法栈(Native Method Stack):和Java虚拟机栈相似,支持本地方法的调用。
    • 堆(Heap):堆是Java内存的核心区域,用来在运行时分配类实例、数组,而栈帧中置存储指向堆中对象或数组的引用。堆上的对象由垃圾回收器进行回收。堆是所有线程共享的。
    • 方法区(Method Area):方法区也是所有线程共享的内存区域。用来存储元数据,如类的结构信息,以及对应的运行时常量池、字段、方法代码等。运行时常量池主要存放各种常量,也包括类方法的符号引用。

    堆内存模型

    堆内存模型

    堆内又分为新生代、老年代、永久代。当最大堆体积大于最小堆体积的时候,堆创建的时候不会占用全部的堆空间,因此留下一部分暂时未被使用的空间,就是Virtual。

    1. 新生代:大部分对象都在新生代中创建和销毁(小部分大对象直接在老年代创建),新生代内部分为一个Eden和两个Survivor。
    2. 老年代:放置长生命周期的对象。对象在新生代中达到一定的年龄之后会拷贝到老年代。如果对象太大,在新生代找不到连续的空间存放,会直接在老年代创建。
    3. 永久代:存放类元数据、常量池等,JDK 8移除了永久代,增加了元数据区。

    堆空间中的垃圾回收

    通常情况下,垃圾回收的流程如下:

    1. 大部分的Java对象都在Eden空间中创建,这时候两个survivor空间是空的。当Eden空间达到一定的阈值之后,触发Minor GC,将Eden中存活的对象复制到其中一个survivor中,并标记对应的年龄+1,这时候Eden空闲下来。
    2. 当Eden空间再次达到一定的阈值之后,将空的survivor作为to,有对象的survivor作为from,将Eden和from中存活的对象复制到to中,并标记对应的年龄+1。
    3. 第二步发生多次之后,有的对象存活的年龄会到达一定的阈值,复制到老年代。
    4. 老年代会发生的GC叫Major GC,取决于JVM的实现方法,一般采用标记整理,将老年代的对象进行标记,清除无用对象之后将其它对象整理到连续的内存区域。

    常用的JVM的垃圾回收算法

    如何确定对象是否需要回收

    1. 引用计数算法:给对象添加一个引用计数器,当对象被引用的时候就将引用计数器+1,当引用失效时,将计数器减1。计数器为0的对象就是可以回收的对象。Python使用的就是引用记数算法。但Java没有使用,因为存在循环引用的问题。
    2. 可达性分析算法:选定一些活动的对象作为GC Roots,根据对象的引用链向下搜索。如果某一个对象和GC Roots之间没有引用链条,即不可达,就视为可回收对象。GC Roots是Java虚拟机栈、本地方法栈中正在引用的对象、静态属性引用的对象和常量引用的对象。

    常见的垃圾回收算法

    • 复制算法:将内存划分成2块,每次只使用一块,当触发GC的时候,将存活的对象复制到另一块内存中,然后将已使用的那块内存空间进行清理。复制算法运行高效,但由于要将内存空间分成2块,所以对内存空间的使用效率较低。
    • 标记-清除算法:标记清除算法分为标记和清除两个阶段。标记阶段对可回收对象进行标记,清除阶段对标记的对象进行回收。标记清除算法效率不高,并且在回收对象之后,产生大量不连续的内存空间,导致后续更容易触发GC。
    • 标记-整理算法:和标记清除算法相似,只是在标记之后,不直接清除可回收对象,而是将存活对象挪向内存的一端,然后将剩下的内存空间进行清理。可以解决标记清除算法的内存空间碎片问题。

    PS: 文中图片引用自极客时间Java核心技术36讲。

    极客时间分享

    相关文章

      网友评论

        本文标题:JVM内存模型和垃圾回收

        本文链接:https://www.haomeiwen.com/subject/enbzwftx.html