美文网首页
你不得不知道的JVM(上)

你不得不知道的JVM(上)

作者: 小驴小驴 | 来源:发表于2021-09-16 11:12 被阅读0次

    一、JVM 与 JDK之间的联系

    JDK是一个大集合,囊括了JRE、JDK工具(javac、javap...),同样包括了最为重要的JVM

    二、JDK自带工具

    2.1 Javap 工具

    Javap工具可以将无法识别(除非借助查看JVM Class字节码文件的协议信息才能读懂),因此通过Javap反编译工具将字节码文件重新反编译成能读懂的文件

    2.2 JStack

    检测线程情况,判断是否存在死锁

    2.3 JConsole

    图形化界面查看JVM 运行时数据区的使用情况,垃圾回收的次数与时间

    2.4 JVisualvm

    同JConsole,功能更加强大

    三、JVM为何能够跨平台

    比如 Groovy、Java、Scala、kotlin都能依托JVM运行,是因为这些语言会将源代码编译成符合JVM约束的Class文件

    四、Java类加载流程

    Java的类加载依赖于ClassLoader,ClassLoader会将Class字节码文件加载到内存中,并负责进行下面的加载、链接、初始化过程

    • 加载
      将Class文件转换为Kclass数据结构,并存放在方法区中,并在堆中生成一个java.lang.class对象作为对方法区Kclass数据对引用;
      其中两者互相持有指针。
    • 链接
      • 验证
        主要是验证Class文件的正确性,如Magic、版本等信息
      • 准备
        为静态变量分配内存,并初始化成默认值
        对于 static final修饰的变量,对于基本数据类型或者String类型数据,还是在准备阶段就进行了赋值,而不会进入到初始化阶段才会赋值
      • 解析
        将类中的符号引用转换为直接引用
    • 初始化
      在初始化的过程中,如果发现类还没有加载与链接,则会先进行加载与链接;
      指定class文件中已经构造好clinit的方法,clinit方法中包含了:父类的静态变量初始化、父类静态代码块、当前类静态变量初始化、当前类静态代码块;需要注意的是clinit方法只会随着类的加载只执行一次;
      触发时机?比如Class.forName就会导致初始化;这里就有个常规的面试题:为什么加载数据库驱动需要使用此方式?

    五、Class.forName 与 ClassLoader的区别

    5.1 Class.forName

    Class.forName有两种参数形式方法:

    • Class.forName(String name)
    • Class.forName(String name, boolean initialize, ClassLoader loader )

    在第二个方法中,initialize参数就是控制加载的时候是否进行初始化,默认为True.

    5.2 ClassLoader.loadClass

    方法签名:

    • ClassLoader.loadClass(String name)

    该方式无法进行 初始化 操作。

    5.3 比较

    两者都可以保证类加载中的 加载链接

    但Class.forName默认可以进行初始化操作,而ClassLoader方式不会

    六、ClassLoader类加载器

    6.1 分类

    • Bootstrap ClassLoader
      负责加载JRE中核心类等等,如:rt.jar
    • Extension ClassLoader
      负责加载JRE中lib/ext目录中的包,因此,如果我们自定义的一些Class可以放在这个目录中
    • Application ClassLoader
      应用程序中的所有类都由该加载器加载
    • 自定义ClassLoader

    6.2 加载流程:双亲委派机制

    这是类加载机制中需要遵守的规则

    • 检查某个类是否已经被加载
      自底向上
    • 加载某个类
      自顶向下

    6.2.1 双亲委派机制的意义

    • 避免相同类的重复加载(相同类只会加载一次)
    • 保护Java核心类免遭破坏(比如你重新写一个String类,在加载你应用级别代码时,先会看String类是不是已经被其它类加载器加载了)

    七、JVM组成部分(简述)

    这部分主要由两部分组成,分别是 运行时数据区执行引擎两部分组成,下面将进行阐述

    7.1 运行时数据区

    在上述部分中详细介绍了class字节码加载到JVM的过程,那么如何组织这些Class文件中不同的结构以及Class中的指令该如何运行,这些就是运行时数据区应该考虑的事情,而方法区中的所有区域生命周期也不同,有的区域生命周期同JVM生命周期,有的区域的生命周期同应用程序中的线程生命周期

    运行时数据区的组成部:

    • 方法区
    • 本地方法栈
    • 程序计数器

    7.1.1 方法区

    • 线程共享,线程不安全,生命周期同JVM
    • 方法区中存放内容如下:
      • 已经被类加载器加载的Class信息,表现上为KClass数据结构
      • 类静态变量(JDK1.6及之前)
      • 运行时常量池
      • 即时编译后CPU可直接执行的机器码
    • 方法区是一种标准。其实现在不同版本JDK中各有不同
      在JDK1.7及之前使用的是永久带的实现方式,在JDK1.8开始使用的是元空间实现方式
    • OOM
      当方法区内存不足时会抛出OOM

    7.1.2 堆

    • 线程共享区域

    • 主要用于存储各类对象以及数组

    • 生命周期同JVM

    • OOM

      当堆空间不足时会抛出OOM

    7.1.3 栈

    • 每个线程所独立存在的区域,因此线程安全
    • 是一种栈数据结构
    • 当线程需要执行一个方法时,就对应着栈中的栈桢(Frame)结构,当线程需要执行某个方法时,就压入栈,当执行完毕之后再弹出栈

    7.1.4 程序计数器

    线程安全,记录当前线程正在执行的指令地址,用于假设当前线程失去了CPU执行权,当重新获取CPU执行权时,能够恢复之前的线程继续完成任务

    7.1.5 本地方法栈

    线程安全,结构同栈,只是本地方法栈执行的是标注有native关键字的方法,而这些方法是由C、C++编写的。

    7.2 执行引擎

    在执行引擎中包括了三个部分,下面逐一介绍

    7.2.1 即时编译器

    当开启了开选项之后(默认开启),会动态侦听热点代码,当发现有热点代码之后,通过即时编译器可以将某一段热点代码编译+解释形成CPU可直接执行的机器码指令,并保存在方法区,之后直接可以运行,从而提高热点代码的执行效率

    7.2.2 垃圾回收器

    这也是Java流行的原因之一,因为Java有垃圾回收器,从而不需要开发人员自己去释放垃圾

    7.2.3 解释器

    之前面试有个常考的面试题:Java是编译形语言还是解释形语言?其实Java是两者都有兼顾到

    Java先利用Javac工具将Java代码编译成Class字节码文件,当类加载器将Class字节码文件加载到JVM之后,真正运行字节码指令时,还是需要解释器去将指令翻译成CPU可识别的指令。

    相关文章

      网友评论

          本文标题:你不得不知道的JVM(上)

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