Java虚拟机笔记

1、JDK&JRE&JVM

  • Java 执行流程

    image-20230818080111014

  • JRE的应用

image-20230818080437924

  • JDK

image-20230818080501387

  • JDK+JRE+JVM

    • JDK(Java开发环境):JRE+工具(编译器、调试器、其他工具等)+类库

      编译器:将Java 文件编译为class文件,也是JVM能运行解释的文件

    • JRE(Java 运行环境):JVM+Java解释器

    • JVM:Java虚拟机

2、Java字节码文件结构

image-20230818081451559

  • 官方字节码文件结构

image-20230818081730077

  • 什么是u2u4

u2:代表数据占2个字节, u4:代表4个字节。

  • JDK编译对应版本号

JDK7-->51 JDK8-->52 JDK-->53 ……

image-20230818082632044

  • 编译的本质

    编译的本质就是Java源文件转换为JVM认识的16进制class文件格式。JVM虚拟机不只是为Java服务,只要符合class标准格式的16进制JVM就能识别。

3、类的加载机制

3.1 类加载大概流程

image-20230818083133336

  1. 装载:获取类的全限定类名,将class文件转化为二进制流。

    1. 将二进制流中类的描述信息存入方法区。如:创建时间、版本等……

    2. 将java.lang.Class对象放入内存。

image-20230818083752782

  1. 链接

    1. 验证:验证被加载类的正确:如文件的格式,文件元素等。

    2. 准备:在方法区中为静态变量分配空间,并设置初值。

    3. 解析:把类的符号引用转换为直接引用。

image-20230818085136385

3.初始化

为类的静态变量设置默认值,执行静态代码块。

image-20230818085830157

①装载---->②链接(验证,准备,解析)---->初始化

image-20230825104559030

第一步:加载指的是把class字节码文件从各个来源通过类加载器(包括启动类加载器,拓展类加载器、应用类加载器、用户自定义加载器)装载入内存中。

流程是,获取全限定类名-->将class字节文件转化为二进制流-->将二进制流中类的描述信息放入方法区。将java.lang.Class对象放入堆内存。

第二步:链接

  • 验证:比如文件格式的验证,元数据的验证(如是否继承了final修饰的类),字节码的验证(保证语义的准确性),符号引用的验证。

  • 准备:主要为静态变量(类变量)分配内存,并赋予初值(初值,不是代码中具体写的初始化的值)。

比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456

  • 解析:符号引用替换为直接引用的过程。

第三步:初始化

主要是对类变量初始化,是执行类构造器的过程。

拓展:为什么会有自定义类加载器?

  1. 一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。

  2. 另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

3.2 类加载器

3.2.1 分类

不同的类加载器加载不同的类:

启动类加载器(Bootstrap classloader):主要负责加载Java中的一些核心类库,主要位于<JAVA_HOME>/lin/rt.jar中。

拓展类加载器(Extension classloader):主要加载一些拓展类,是启动类加载类的子类。

应用类加载器:(System classlzhuyoader):主要用于加载CLASSPATH 路径下,我们自己定义的类,是拓展类加载器的子类。

image-20230818092121435

这由于双亲委派机制模型导致的一个原因。

3.2.2 双亲委派模型

如果一个类加载器收到了类的加载请求,他不会自己去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在其父类加载器,则进一步加载向上委托,一次递归,请求最终达到顶层的启动类加载器返回可以完成类加载任务,就成功返回。倘若父类加载器无法完成此次加载任务,子加载才会尝试自己去加载。

image-20230818094519585

  • 如何打破双亲委派机制? 例子:Tomcat

    自定义类加载器类,继承Classloader类,重写loadClass()方法.

4、JVM内存模型

4.1 什么是JVM内存模型

JVM需要使用计算机的内存,Java程序运行中所处理的对象或者算法都会用使用JVM的内存空间,JVM将内存划分为5块,这样的结构称为JVM内存模型。

4.2 JVM为什么进行内存区域划分

随着对象数量的增加,JVM内存使用率也在增加,如果JVM内存使用率达到100%,则无法继续运行Java程序。为了让JVM内存,我们需要对JVM内存进行回收。为了提高垃圾回收效率,JVM将内存区域进行了划分。

4.3 JVM内存划分

JVM按照线程是否独享将内存首先分为两大类。

  • 线程独享区

只有当前线程能访数据的区域,线程之间不能共享。

线程独享区随线程的创建,随线程的销毁而被回收。

  • 线程共享区

    所有线程都可以访问的区域。

当线程被销毁的时候,线程共享区的数据不会立即回收,需要等待达到垃圾回收的阈值之后才会进行回收

  • 模型图

image-20230818103022944

image-20230818103631152

栈内存独立:基本数据类型

堆内存共享:引用数据类型数据

4.3.1 程序计数器

image-20230818113640698

程序计数器会记录当前线程要执行指令的内存地址,只占用一小部分的内存区域,只记录一个地址,所以我们认为程序计数器是不会出现内存溢出的问题的分区。

所以虚拟机是不会优化程序计数器区域的。

4. 3.2 本地方法栈

Java中有些代码的视线是依赖于其他非Java语言的(C++),本地方法栈存储的是维护非Java语句执行过程中产生的数据,一般本地方法栈不会出现内存问题。

4.4 虚拟机栈

存放当前线程中所有声明的变量,包基本数据类型和引用数据类型的引用。

  • 基本数据类型和引用数据类型的划分标准

    • 基本数据类型:变量在声明的时候,能够确认占用内存的大小。

    • 引用数据类型:变量在声明的时候不知道占用内存大小。

      引用数据类型将值的引用放到虚拟机栈中,而对象则存在对内存中,引用数据类型占用4个字节存放地址。

image-20230818150630402

4.4.1 栈帧的结构

image-20230818150923077

image-20230818151247199

  • 常量池的应用中的常量存放的你是值。而是 方法区中常量池中的常量的地址。

image-20230818151731876

  • 局部变量:存放当前方法的局部变量,基本数据类型存储值。引用数据类型存储堆内存中的地址。

  • 操作数栈:对方法的变量提供计算的区域。

  • 常量数据的引用:常量数据会存放到方法区的常量池,不管是基本数据类型还是引用数据类型都会存放常量池的地址。

  • 方法返回值的地址:方法返回数据会存到计算机内存的寄存器中。

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明天码上有钱啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值