美文网首页
深入理解JVM系列-1

深入理解JVM系列-1

作者: 一只遨游在编程中的小菜鸟 | 来源:发表于2023-05-30 16:19 被阅读0次

    一、java内存模型

    要深入了解jvm之前,需要对java内存结构有清楚的认识,我们先通过一个简单的demo来了解java内存模型。
    有如下两个类,我们通过它的执行顺序来观察内存中的变化

    class A{
      static B b = new B();
    
      public static void main(String[] args){
        fun1();
        fun2();
      }
    
      public static void fun1(){
        B b1 = new B();
        b1.load();
      }
    
      public static void fun2(){
        b.load();
      }
    }
    
    class B{
      public void load(){}
    }
    

    当class A中的main方法启动的时候内存中需要做的操作

    1、加载类A,B到java内存中

    类加载到内存中的时候,我们必然有一块区域来存放这些加载的类的数据,这块区域我们称之为方法区,在jdk8之后方法区的概念被元空间(Metaspace)取代。元空间相比于方法区灵活很多。比如可以动态分配元空间大小,通过指针和其他方式来表示类和元数据,可以更有效的利用内存。为了统一概念,我们接下来的介绍全部称之为方法区。
    如下图,会把A,B的字节码文件加载到内存中的方法区。

    1.png

    2、执行main方法

    类加载之后执行main方法的时候也有一块区域来对应方法的执行,称之为栈。栈的数据结构决定了他的特点 先进后出。main方法执行的时候需要先开启一个main线程来执行字节码文件。而java又可以支持多线程,此时就需要一个东西来记录每个线程执行到哪一行代码。这个记录代码行数的东西叫程序计数器。那么此时的内存模型为:


    2.png

    当main方法执行的时候会调用fun1和fun2。在fun1中创建了一个对象b1,fun2中使用了静态对象b(静态对象的初始化随着类的加载而加载,为了更好的延申出堆的概念故在此处提出)。这些对象的创建需要对应一块内存区域,此块内存区域就是堆。
    此时的内存模型为:


    3.png

    3、方法执行完毕释放资源

    当所有方法执行完之后,方法需要出栈(又成弹栈),此时可以看到main方法在最下面(压栈),先进后出因此fun2,fun1,main依次出栈。出栈之后栈里面内存得到释放,但是堆里面的对象此时b还是被static b引用,而b1此时没有任何人引用。所以b1对应的堆内存就成了垃圾需要被释放。但是java的内存回收机制并不是没有引用就立马会回收。当堆内存不足的时候才会触发内存回收机制,jvm才会开始回收这些内存垃圾。

    二、新生代,老年代、永久代的概念

    1、永久代:永久存在与jvm内存中的数据。方法区就是永久代。

    2、新生代:新创建的对象所在的位置。

    还是看上图的内存模型,每次new出来的对象都会放到堆里面,这批对象存储的位置在堆中又有细分,分为新生代和老年代,新生代就是用于存方这些对象的地方。

    3、老年代:年纪大的对象。

    当堆内存快要满的时候,会触发jvm垃圾回收机制,此时无引用的内存垃圾就会被释放掉,有引用的对象会被保留,每经过一次垃圾回收被保留下来的对象的年龄就会+1。jvm默认年龄>15的对象就会被移入老年代。还有一种情况会放入老年代,后面再讲。

    三、Eden区和Survivore区

    系统的内存都是连续的,当需要使用内存的时候会在空闲内存之后连续开辟一块新的内存以供使用。而jvm触发垃圾回收机制时,回收新生代无引用的数据,当这些垃圾被回收之后就会出现一种情况,被留下的有引用的数据之间存在内存碎片,此时就会导致内存不再连续。会影响接下来的内存分配使用。因此衍生出了E区和S区的概念。

    所有new出来的对象全部放到E区,当E区要满的时候触发新生代垃圾回收机制(Young GC 或者叫Minor GC),此时会把E区所有垃圾全部释放,并且把存活下来的对象使用复制算法重新整理内存放入到S区,并把这批对象年龄+1。这样就保证了E区的空间永远是连续的。

    但是这样还有问题,第二次垃圾回收的时候如何处理,因为此时S区被上一次存活的对象占用了空间,这一批存活的对象该如何整理内存,该放在哪里呢?
    对于这一问题,其实也很简单,再增加一个S区即可,永远空出来一个S区用于整理内存和存放存活对象。因此,堆内存空间整体模型就出来了。


    image.png

    四、哪些对象会被回收

    大致了解了新生代模型之后我们来想一个问题,jvm如何判断什么数据需要被回收的。

    jvm每次内存回收的时候都会采用一种 可达性分析算法 来判定该对象是否可以被回收。这个算法的意思就是分析谁在引用这个对象 一级一级向上找,看是否有GC Roots。

    那什么又是GC Roots 呢,GC Roots有一下几种
    1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
    2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
    3、在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
    4、本地方法栈中 JNI(Native方法)引用的对象
    5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
    6、所有被同步锁(synchronized关键字)持有的对象。
    6、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
    7、根据用户所选用的垃圾收集器以及当前回收的内存区域不 同,“临时性”地加入的其他对象。
    在我们代码中常见的GC Roots 主要包含:栈正在使用的对象,局部变量,静态变量。

    java中的引用分为强引用,弱引用,软引用,虚引用。此次不在延申这些引用的用法。对与垃圾回收来说,强引用 并且有GC Roots的对象不会被回收。其他引用不管有没有GC Roots都会被回收。

    未完待续~~

    相关文章

      网友评论

          本文标题:深入理解JVM系列-1

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