Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈

【摘要】

JVM(Java Virtual Machine)作为Java研发人员工作的每天都会接触到的虚拟机,其运行机制与底层原理想必大家都略知一二,今天我将从初学者的角度出发,结合甲骨文官方的技术文档,对部分Java虚拟机的相关知识做一些简单的梳理。

(Oracle官方文档地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)

目录

一        JVM(Java Virtual Machine)简介... 3

1.1        源码文件到.class文件的过程... 3

1.2        如何阅读字节码文件... 3

二    类加载过程... 4

2.1        装载... 4

2.2        链接... 4

2.2.1         验证:保证被加载类的正确性... 4

2.2.2         准备... 5

2.2.3         解析... 5

2.3        初始化... 5

三    类加载器与双亲委派模型... 5

3.1        类加载器基础概念... 5

3.2        双亲委派机制基础概念... 6

3.3        优势... 6

3.4        安全性... 6

3.5        如何破坏双亲委派模型... 7

四    运行时数据区... 8

4.1        方法区... 8

4.2        堆... 9

4.3        Java虚拟机栈... 9

4.3.1         栈帧... 10

4.3.2         理解栈帧... 11

4.4        程序计数器... 12

4.5        本地方法栈... 12

五    参考文献... 12

一    JVM(Java Virtual Machine)简介

Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有其自己相相应的指令系统。

Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。

图1:JDK与JRE关系图

1.1       
源码文件到.class文件的过程

使用javac编译 Person.java--->Person.class

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

1.2       
如何阅读字节码文件

甲骨文官方文档中有说明解释(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1),文档中对字节码.class文件做了如下解释:

class file consists of a
single ClassFile structure:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

The items in the ClassFile structure are as follows:magic

The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE.

说明:编译后的字节码文件通过16进制编码后就形成了可阅读的格式,参照官方文档的说明:u4代表的含义就是前8位16进制数,含义即文件格式magic的值为:0xCAFEBABE

二   类加载过程

2.1        装载

(1) 通过一个类的全限定名获取定义此类的二进制字节流

(2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

(3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

2.2        链接

2.2.1       验证:保证被加载类的正确性

文件格式验证/元数据验证/字节码验证/符号引用验证

2.2.2       准备

为类的静态变量分配内存,并将其初始化为默认值(比如整型此时初始化为默认值0)

2.2.3       解析

把类中的符号引用转换为直接引用(指向真实的内存物理地址

2.3        初始化

对类的静态变量、静态代码块执行初始化操作。(赋予真实的变量值)

图2:类加载过程

 

三   类加载器与双亲委派模型

3.1        类加载器基础概念

图3:三种类加载器的层级关系

3.2        双亲委派机制基础概念

定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

图4:不同加载器负责的目录范围和功能的差异

3.3        优势

Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的

Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。

3.4        安全性

这种模式也一定程度上出于安全考虑,避免用户自己编写的类动态替换 Java 的一些核心类,比如 String,同时也避免了重复加载,因为 JVM 中区分不同类,不仅仅是根据类名,相同的 class 文件被不同的 ClassLoader 加载就是不同的两个类,如果相互转型的话会抛java.lang.ClassCaseException的异常。

3.5       
如何破坏双亲委派模型

我们来看看Java的源码内容:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);
 
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }}

这是public abstract class ClassLoader这个类中的loadClass方法代码片段的粘贴,我们从标记红色的代码块可以看到它寻找父类加载器的方法,显而易见,只要我们在自己的类加载器中重写这个loadClass方法,就可以破坏类加载的双亲委派机制

四   运行时数据区

图5:运行时数据区示意图

4.1        方法区

官方文档原文:

翻译:

简单总结:

(1) 方法区是各个线程共享的内存区域,在虚拟机启动时创建

(2) 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

(3) 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来

(4) 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

(5) 方法区在JDK 8中就是Metaspace【元空间】,

在JDK6或7中就是Perm Space【永久代】。

(6) Run-Time Constant Pool(常量池)在方法区分配

4.2        堆

官方文档原文:

懒得贴了,有兴趣的话给大家网址自己去看吧

(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)

翻译:(勉强贴一下)

总结:

(1)       堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享

(被多个线程共享,首先想到的就是这部分的数据是线程不安全的,堆的生命周期跟虚拟机的生命周期一致)。

(2) Java对象实例以及数组都在堆上分配。

(3) 当堆无法满足内存分配需求时,将抛出OutOfMemoryError异常。

4.3        Java虚拟机栈

官方文档原文:

(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)

翻译:

懒得翻译了,大家自行谷歌,不太想凑字数。

总结:

Java虚拟机栈

(1) 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是f,独有的,随着线程的创建而创建。

(2) 每一个被线程执行的方法,为该栈中的栈帧,即每个方法的执行对应一个栈帧

(3) 调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出

4.3.1       栈帧

每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。

每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接、方法返回地址(Return Address)和附加信息。

(方法返回地址:记录上一级方法调用该方法后回调的行数位置)

举例:有如下方法:

图6:一个三层链式调用的方法

那么对应的栈帧操作过程就是:

图7:Java虚拟机栈对应的栈帧操作过程

4.3.2       理解栈帧

源码文件:

编译后的字节码文件含义(顺序执行):

图8:虚拟机栈示意图

4.4        程序计数器

官方文档原文:

(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)

别问,问就是自己翻译。

一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置

(1)程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。

(2)如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。

(3)如果正在执行的是Native方法,则这个计数器为空。

4.5        本地方法栈

官方文档原文:

(https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2)

嘿嘿嘿,鸡汤来咯!

一句话明白本地方法栈:如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。

五   参考文献

1. 《The Structure of the Java Virtual Machine》https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.2

2. 《Java 语言和虚拟机规范》

https://docs.oracle.com/javase/specs/index.html

3. 《The class File Format》

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1

4. 《Java 虚拟机 规范》

https://docs.oracle.com/javase/specs/jvms/se10/html/index.html

Java类加载机制与JVM运行时数据区各逻辑内存区域与JDK的版本相关差异浅谈的更多相关文章

  1. Java内存管理:Java内存区域 JVM运行时数据区

    转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...

  2. JVM运行时数据区和垃圾回收机制

    最近参考各种资料,尤其是<深入理解Java虚拟机 JVM高级特性和最佳实践>,大牛之作.把最近学习的Java虚拟机组成和垃圾回收机制总结一下. 你不会的都是新知识,学无止境,每天进步一点点 ...

  3. Jvm运行时数据区 —— Java虚拟机结构小记

    关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...

  4. Java中的字符串常量池和JVM运行时数据区的相关概念

    什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...

  5. Jvm运行时数据区

    一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...

  6. JVM 运行时数据区 (三)

    JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...

  7. JVM运行时数据区及对象在内存中初始化的过程

    JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...

  8. JVM总结(一):概述--JVM运行时数据区

    大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...

  9. JVM 运行时数据区(二)

    @ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...

  10. JVM运行时数据区与JVM堆内存模型小结

    前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...

随机推荐

  1. 使用wxpython开发跨平台桌面应用,基类列表窗体的抽象封装处理

    在开发一套系统框架的时候,除了关注实现系统的功能实现外,我们对于系统的各个方面都是应该精益求精,以最少的编码做最好的事情,在开发的各个层次上,包括前端后端,界面处理.后端处理.常用辅助类.控件封装等等 ...

  2. mysql8创建用户

    create user test_user@'%' identified by 'test2022@'; grant all privileges on test.* to test_user@'%' ...

  3. 鸿蒙NEXT开发案例:二维码的生成与识别

    [引言] 在本篇文章中,我们将探讨如何在鸿蒙NEXT平台上实现二维码的生成与识别功能.通过使用ArkUI组件库和相关的媒体库,我们将创建一个简单的应用程序,用户可以生成二维码并扫描识别. [环境准备] ...

  4. ElementUI ---- dialog点击取消后蒙遮层不消失

    场景: 页面A打开了 dialog, 然后点击 页面A dialog 的按钮 跳转到 页面B,并且打开页面B的 dialog 但是页面B的 dialog 关闭后,蒙遮层并没消失(已经设置了 :appe ...

  5. SpringMVC源码剖析(三)- DispatcherServlet的初始化流

    在我们第一次学Servlet编程,学java web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转 ...

  6. JS之Date时间处理

    初始化当前时间: // 1. 使用构造函数方式 var newDate = new Date() // 2. 使用函数方式 var date = Date() // 返回的是一个Date对象 cons ...

  7. 自动化部署之Gitlab+Jenkins+Docker

    总结自动发布流程:  Gitlab+Jenkins+Docker 一般部署方式: 1.外挂方式: 就是将实际的代码挂载到宿主机上,docker中提供程序运行的环境, 这样的话只需要更新对应的代码就够了 ...

  8. Jenkins篇-权限管理

    我使用 权限管理插件是Role Strategy Plugin,他可以对构建的项目进行授权管理,让不同的用户管理不同的项目,将不同环境的权限进行区分 1)安装插件 系统管理>插件管理查找Role ...

  9. Codeforces Round 859 (Div

    F. Bouncy Ball 给定\(n×m\)矩形,起点\(st\),终点\(ed\),有一小球从起点出发,每次可以选择4个方向,如果碰到边界就反弹,询问最后能否到达终点 题解:\(DFS\) + ...

  10. openwrt交换机配置命令-swconfig

    swconfig swconfig 是交换接口 (switch) 配置命令. 交换机是二层设备,是我们用来配置vlan的必备利器. 使用swconfig list可以列出当前可用的 SWITCH 设备 ...