JVM内存模型(java虚拟机垃圾回收)

JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些数据在JVM启动的时候创建,在JVM退出的时候销毁。而其他的数据依赖于每一个线程,在线程创建时创建,在线程退出时销毁。

《JVM内存模型(java虚拟机垃圾回收)》

存区域分为本地方法栈、虚拟机栈、堆、程序计数器、方法区等,方法区又被称作永久代。

程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

Java虚拟机栈

    与程序计数器一样,Java虚拟机栈也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈桢用于存储局部变量表,操作数栈,动态链接,方法出口等信息,下图为栈桢结构图:

《JVM内存模型(java虚拟机垃圾回收)》

GC如何发现垃圾

       1.引用计数法:对象一旦被使用或者引用,计数器会进行+1和-1操作,当为0时被标示为垃圾。此种标示方法的弊端在于已经死亡的对象如果存在相互引用,则会一直占用内存空间,不被回收。此时会导致内存泄漏。为了解决这个问题出现了根搜索算法

2.根搜索算法

《JVM内存模型(java虚拟机垃圾回收)》

根搜索算法的基本思路是通过一系列的“GC Roots”的对象作为起始点,从这些节点开始往下搜索,搜索的走过的路径称为引用链,当一个对象到“GC Roots”没有引用链可达时(也就是用图论的话说就是从GC Roots到这个对象不可达),则证明此对象是不可用的,这样的对象被判定为是可回收的。

java中可以作为GC Roots对象包括以下几种:

2-1.虚拟机栈(栈帧中的本地变量表)中的引用对象。

2-2.方法区中的类静态属性引用的对象。

2-3.方法区中的常量引用的对象。

2-4.本地方法栈中JNI(也即一般说的Native方法)的引用的对象。

3.GC如何清除垃圾

  1. 标记-清除:
    这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。
  2. 复制算法:
    为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
    于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代)
  3. 标记-整理
    该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。
  4. 分代收集 
    现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。

4.内存泄漏
        本该被回收的内存,遗漏了,一直占用

    5.内存溢出
        分配给进程内存满了
        5.1.堆内存溢出:
            OutOfMemoryError: Java heap space
        5.2.方法区(永久代)溢出:
            OutOfMemoryError: Metaspace
        5.3.栈内存溢出:
            StackOverflowError

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/qq_42586493/article/details/82287915
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞