• 关于JVM:内容以及流程释义


    对于JVM、GC、类加载,很多人摸不清楚头绪,不知道他们之间的关系。误以为GC和类加载还有JVM区分统称垃圾回收,实则他们包含的东西很多,很细,完整的了解正个JVM的加载过程,就需要全面理解这些东西。

    我认为的理解相互关联步骤:

    类的加载过程

    通过类的加载延伸到gc的编译原理

    根据gc得到解决方案并拓展锁知识

    根据这些完全熟悉gc的生态链

    下面开始开始简述相关知识。

    1.内存区域类型

    *.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制;

    *. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(对象可能在常量池里)(字符串常量对象存放在常量池中。);

    *. 堆:存放所有new出来的对象;

    *. 静态域:存放静态成员(static定义的);

    *. 常量池:存放字符串常量和基本类型常量(public static final)。有时,在嵌入式系统中,常量本身会和其他部分分割离开(由于版权等其他原因),所以在这种情况下,可以选择将其放在ROM中 ;

    *. 非RAM存储:硬盘等永久存储空间

    2.堆栈空间分配

    栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表

    3.堆栈缓存方式

    栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。

    堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

    4、数据结构中的堆和栈

    堆和栈在数据结构中是两种不同的数据结构。 两者都是数据项按序排列的数据结构。

    栈:像是装数据的桶或者箱子

    栈是大家比较熟悉的一种数据结构,它是一种具有后进先出的数据结构,也就是说后存放的先取,先存放的后取,这就类似于我们要在取放在箱子底部的东西(放进去比较早的物体),我们首先要移开压在它上面的物体(放入比较晚的物体)。

    堆:像是一颗倒立的大树

    堆是一种经过排序的树形数据结构,每个节点都有一个值。通常我们所说的堆的数据结构是指二叉树。堆的特点是根节点的值最小(或最大),且根节点的两个树也是一个堆。由于堆的这个特性,常用来实现优先队列,堆的存取是随意的,这就如同我们在图书馆的书架上取书,虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书,书架这种机制不同于箱子,我们可以直接取出我们想要的书。

    一.类加载

    1.类加载的过程

    在这里插入图片描述

    (1)将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。

    (2)分为三个阶段:

            1)验证阶段:验证文件的安全性,防止对jvm本身造成危害;
    
            2)准备阶段:正式为类变量(static变量)分配内存并设置类变量初始值
    
            3)解析阶段:虚拟机常量池的符号引用替换为字节引用过程
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)执行类构造器初始化类对象;

    总结:初始化-加载-销毁

    2.由类加载过程延伸的问题

    (1)类加载器?

       虚拟机提供了3种类加载器:
    
             启动类加载器:主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分;
    
             扩展类加载器:负责加载JAVAHOME路径下的jdk中的一些类库;
    
             系统类加载器:负责加载CLASSPATH路径下的应用程序。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)jvm以什么样的方式加载一个类

       采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式。 那么,一起来理解下双亲委派模式:
    
    • 1

    image.png

    双亲委派:如图,根据自定义类加载器-再到系统类-然后拓展类-最后引导类加载器。如果你在最下层系统类加载器中,编写了一个StringUtil,但是在引导类加载器也有一个相同类,默认只加载系统存在的StringUtil,主要原因是防止修改核心类,保证安全性, 双亲委派模式的好处是避免了一个类会被重复加载。

    3.jvm什么时候决定加载一个类?

       1)创建对象就会触发加载;
    
       2)引用了该类的静态属性、方法都会触发类的加载;
    
       3)初始化子类也会导致父类被加载;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.java中创建对象的几种方式?

       1) new对象
    
       2) 用反射创建对象(代理)
    
       3) 克隆对象
    
       4) 序列化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二.JVM

    JVM定义:指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。

    线程私有区域:程序计数器、Java虚拟机栈、本地方法栈(与线程生命周期一致)

    线程共享区域:Java堆、方法区、运行时常量池

    jvm主要知道它各个模块与作用即可。(小型独立运行的虚拟机)

    三.GC

    1.GC本质上就是对JVM内存管理的一个名词(垃圾回收)。

    栈:和唯一的一个线程强绑定,线程出现就需要这块内存,线程结束,这块内存就可以回收了,这块内存的分配/回收时机是明确的,就不需要 GC 来操心。

    常量池+方法区:首先,这两块内存占比较小,其次,里面的数据很少“失去作用”。回收这块内存性价比不高,也不是重点考虑的。

    堆:GC 的重点就是如何管理堆上的内存。堆上的内存是以对象为单位进行管理的,分配与回收的都是一个完整的对象。因此,回收转换成了回收死对象的问题。

    2.如何判断对象已死

    死对象:没有引用指向的对象。

    1)引用计数法ReferenceCount:无法解决对象的循环引用问题

    给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。 引用计数法实现简单,判定效率也比较高。
    2)可达性分析算法

    核心思想: 通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。

    GC Roots的对象包含下面几种:

       虚拟机栈(栈祯中的本地变量表)中引用的对象
    
    • 1

    方法区中的静态属性不回收,所以有引用指向的对象必须活着,常量引用的对象

    常量池中引用指向的对象

    如果在GC过程中,可达性分析后对象的引用关系发生了变化,有可能造成对象回收错误,所以通常在GC中,会通知应用线程的执行(STW:Stop The World)

    3.引用分类

    1)强引用:被引用的对象必需活着

    2)弱引用:gc就回收

    3)虚引用:和引用无关,但是回收会通知

    4)软引用:被引用的对象尽可能活着,但是内存紧张可以回收

    4.垃圾回收算法

    1)标记清除:标出所有需要回收的对象回收。

    2)复制算法(新生代回收算法):把空间分2区域,把使用的对象复制到新区域,便于区分回收。缺点:需要翻倍内存。

    3)标记整理(老年代回收算法):第一阶段标记被引用对象,第二阶段遍历堆,清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。(清除和复制的结合)

    4)分代收集算法:根据对象存活周期分块,分为新生代和老年代。新生代采用复制,老年代采用标记整理

    对象在Eden区分配

    大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。

    看看 Minor GC和Full GC 有什么不同呢?

    Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。

    Major GC/Full GC:一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。

    Eden与Survivor区默认8:1:1 大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%以上的对象成为垃圾被回收掉,剩余存活 的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回 收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所 以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可

  • 相关阅读:
    docker--基础(一)
    Java中ArrayList和Vector的区别是什么呢?
    Vert.x web 接收请求时反序列化对象 Failed to decode 如何解决?
    《C++ primer plus》精炼(OOP部分)——对象和类(8)
    追问+初心阅读笔记
    回溯常见问题总结
    NAACL2022中Prompt相关论文分类
    ll、chmod 命令
    liunx 运行jmeter脚本jmx
    Node.js:Jest测试框架
  • 原文地址:https://blog.csdn.net/qq_20057315/article/details/128202475