深入Java JVM内存模型(多图解)

目录

1,JVM是什么?

2,JDK,JRE,JVM的关系?

3,JVM的组成

4,类加载器

5,运行时数据区(重点)

(1),程序计数器(线程私有)

(2),虚拟机栈(线程私有)

(3),本地方法栈(线程私有)

(4),方法区(线程公有)

(5),堆(线程公有)

6,总结


//博主画图不易,转载请标明来源。

1,JVM是什么?

JVM》》Java Virtual Macheine 》》Java虚拟机。

实际上JVM是一种用于计算设备的规范,真正使用的是根据这个规范的hotspot VM等虚拟机产品。

这里我们看一下Java执行的过程

JVM从软件层屏蔽了底层硬件指令层的细节,使得开发人员不需要去关注运行环境不同而所提供的底层指令。

2,JDK,JRE,JVM的关系?

简单来说是包含关系,JDK包含了JRE,JRE又包含了JVM,这里我们看一张官方给的图

在JDK中包含编译器等可执行的工具以及JRE中的基本类库接口等等,JRE的底层就是JVM。

3,JVM的组成

4,类加载器

Java加载类的过程

 

类加载过程

加载:主要工作是将类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例(jdk1.8方法区改为Metaspace实现),这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。

验证:负责对字节流进行验证,是否符合jvm的规范要求,是否有影响jvm的运行稳定性。验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等

格式验证:验证是否符合class文件规范

语义验证:检查一个被标记为final的类型是否包含子类;检查一个类中的final方法视频被子类进行重写;确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同)

操作验证:在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否通过富豪引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)

准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。这个时候并不是赋值,只是在内存中为变量分配空间和初始值。

对于该阶段有以下几点需要注意:

这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。这里随着JVM的版本不同,放的位置不同,1.6中放在永久代Perm中,1.7会放在堆空间中开辟,1.8改到了元空间。

这里所设置的初始值通常情况下是数据类型默认的零值(如 0、0L、null、false 等),而不是被在 Java 代码中被显式地赋予的值。

解析:解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

初始化:初始化是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的 Java 程序代码。上面的准备阶段已经为类分配了内存空间和初始值,这里初始化就是根据程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器()方法的过程。

使用:以上步骤全部完成之后,这个类就可以被使用了。

卸载:在使用结束之后,需要将类卸载。

加载的时候我们知道在内存中会有一个java.lang.Class与目标类对应。当代表A类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,A类在方法区内的数据也会被卸载,从而结束A类的生命周期。由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期。

5,运行时数据区(重点)

首先看一下这个区的组成:

(1),程序计数器(线程私有)

用来指向当前线程执行的字节码执行的行号(地址)。

为什么要程序计算器,多线程是时间片轮转使用cpu的,需要记录上次运行的位置,恢复现场。JVM中唯一不会内存溢出的区域。

(2),虚拟机栈(线程私有)

这是Java线程执行方法的内存模型。每个线程对应一个栈,在栈里有栈帧,每个方法对应一个栈帧(存有变量表,操作数栈,动态链接,方法出口等信息),这里不存在垃圾回收问题,只要一个线程结束就释放,所有它的生命周期跟线程一致。

图解:

提问:一个方法一个栈帧,那么递归(方法调用了自己)对应多少栈帧,1个还是n个?n个。

(3),本地方法栈(线程私有)

:本地方法是由其他语言(如C、C++ 或其他汇编语言)编写,编译成和处理器相关的代码。本地方法保存在动态连接库中,格式是各个平台专用的,运行中的java程序调用本地方法时,虚拟机装载包含这个本地方法的动态库,并调用这个方法。本地方法栈用来存放这些方法的接口。

(4),方法区(线程公有)

类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码在此定义。简单来说所有的定义方法信息保存在这里。

静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。

                                                                                         运行时常量池

(5),堆(线程公有)

虚拟机启动时创建,用于存放对象实例和数组,几乎所有的对象都在堆上面分配内存,当对象无法在该空间申请到内存时将会抛出outofMemoryError异常。同时也是垃圾收集器管理(JVM垃圾回收请看这篇https://juejin.im/post/5b85ea54e51d4538dd08f601)的主要区域,可通过XMx-Xms参数来分别指定最大堆和最小堆。

栈是指向堆的,例如在虚拟机栈中的局部变量表中的对象引用指向堆中的对象。

提问:线程公共区域为什么分为方法区和堆?动静分离的思想,方法区不常变动,而堆中经常变动。

堆的图解:

其中永久代其实就是方法区

注:在 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代。被原空间代替。

6,总结

图解整个JVM内存模型。

最后画图不易,转载请以转载发布并标明来源。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值