JVM内存模型

本文详细介绍了JVM的组成部分,包括类加载器(启动类加载器、扩展类加载器、应用程序类加载器和自定义加载器)及其双亲委派模型,以及运行时数据区(如程序计数器、虚拟机栈、本地方法栈、Java堆和方法区)。类加载过程包括加载、验证、准备、解析和初始化。此外,还探讨了执行引擎和本地库接口的作用。最后,讨论了各类内存区域的溢出异常以及相关配置参数。
摘要由CSDN通过智能技术生成

JVM组成

  1. 类加载器
  2. 运行时数据去
  3. 执行引擎

JVM内存模型

1.类加载器

将class文件加载进jvm的方法去,并在方法去中创建一个java.lang.Class对象作为外界访问这个类的接口。实现这个动作的代码模块称为类加载器。

类加载器分类
启动类加载器(Bootstrap ClassLoader)

它是Java加载系统的根部,它负责加载Java核心类库(JAVA_HOME/jre/lib/rt.jar中的类库),由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不同于其他的类加载器,启动类加载器并不继承自java.lang.ClassLoader类。

扩展类加载器

这是一个由Sun的Extension ClassLoader实现的,它负责加载Java的扩展库
(JAVA_HOME/jre/lib/ext或者由系统变量java.ext.dirs指定路径中的类库)

开发者可以直接使用标准扩展目录(ext目录)做类库的扩展。

应用程序类加载器

这是ClassLoader中getSystemClassLoader()方法的返回值,所以也称为系统类加载器
它负责加载环境变量CLASSPATH或者-classpath/-cp命令行选项指定路径中的类库。该类加载器是用户自定义加载器的默认父加载器。

自定义加载器

自定义类加载器(Custom ClassLoader):Java允许我们自定义类加载器,只需要继承java.lang.ClassLoader类,并重写findClass方法即可
这样可以让Java类具有动态加载和热加载的能力,提高了Java程序的灵活性。

类加载过程

1.加载:

  • 加载是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.class对象的过程。
  • 这个过程,将类的.class文件中的二进制数据读入内存,凡在运行时数据区的方法区内。然后就在堆中创建java.lang.class对象,用来封装类在方法去中的数据结构
  • 类加载阶段
  • (1) Java虚拟机将.class文件读入内存,并为之创建一个Class对象。
  • (2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。
  • (3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。
    2.连接:
    2.1验证
  • 验证阶段作用是保住Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害。如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。验证过程分为四个阶段:
  • (1)文件格式验证:验证字节流文件是否符合Class文件格式规范,并且能被当前虚拟机正确处理。
  • (2)元数据验证:是对字节码描述信息惊喜语义分析,以保证其描述的信息符合java语言规范要求
  • (3)字节码验证:主要是惊喜数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机
  • (4)符号引用验证:符号引用验证发生在虚拟机将符号转化为直接引用的时候,这个转化动作将在解析阶段发生。
    2.2准备
  • 准备阶段为变量分配内存并设置类变量的初始化。这个阶段分配的仅为类的变量,而不包括类的实例变量。对于非final的变量,JVM会将其设置成“零值”,而不是赋值语句的:private static int size = 12 。那么在这个阶段,size的值为0,而不是12.但非final修饰的类变量将会赋值成真实的值。
    2.3解析
  • 解析过程是将常量池内的符号引用替换成直接引用,主要包括四类型引用解析。类或接口的解析,字段解析、方法解析、接口方法解析。
    3.初始化:
    类加载过程
双亲委派模型

如果一个类接受到请求,他首先将请求交给父类加载器,直至找到启动类加载器,如果父类加载器加载失败,当前类加载器才会自己加载类。

为什么要使用双亲委派模型:防止类重复加载,保护核心的类

2.运行时数据区

线程共享:方法区、堆
线程私有:虚拟机栈、本地方法栈、程序计数器

程序计数器

线程私有,可以看作是当前线程锁执行的字节码的行号指示器,字节码解析器的工作是通过改变装个技术值来读取下一条要执行的字节码指令。
多线程是通过线程轮流切换并分配处理器执行时间来实现的,任何一个时刻,一个内核只能执行一条线程中的指令,为了线程切换后能恢复正确的执行位置,每条线程都需要一个独立的程序计数器,这就是一开始说的“程序私有”。如果线程正在执行的方法是java方法,计数器记录的是虚拟机字节码的指令地址;如果是Native方法,计数器值为空。

Java虚拟机栈

线程私有,生命周期和线程相同,java虚拟机栈面试的是java方法内存模型,每个方法在执行时会创建一个栈帧,存储局部变量、操作数栈(在程序运行过程中操作的一块临时的内存空间)、动态链接(方法的内存地址)、方法出口信息,每一个方法从调用到结束,就对应一个栈帧在虚拟机栈中的进栈和出栈过程。局部变量表保存了各种基本数据类型(int、double、char、byte等)、对象引用(不是对象本身)和returnAddress类型(指向一条字节码地址)。

栈空间大小设置:-Xss 为jvm启动的每个线程分配的内存大小。

线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError;
虚拟机栈扩展时无法申请到足够的内存,抛出OutOfMemoryError。

本地方法栈

上述虚拟机为JVM执行java方法服务,本地方法则为执行Native服务,其他和虚拟机类型,也会抛出StackOverflowError、OutOfMemoryError

Java堆

常说的“栈内存”、“堆内存”,其中前者指的是虚拟机栈,后者说的就是java堆了,java堆是被线程共享的。在虚拟机启动时被创建,java堆是java虚拟机所管理的内存中最大的一块。java堆的作用是存放对象实例,java堆可以处于物理上不连续的内存空间,只要求逻辑上连续即可。
java堆是垃圾收集器管理的主要区域,因此很多时候也被称作“GC堆”,从内存回放的角度看,现在收集器基本都采用分代回收算法,所以java堆还可以细分为:新生代,老年代。再细致一点的有Eden空间、fromSurvivor空间、To Survivor空间。

堆的空间大小设置:- Xms java堆启动是内存 -Xmx java堆可扩展的最大内存
方法区

也被称为永久代,是线程共享的区域。存储已被虚拟机加载的类信息、常量、静态常量、即编译器编译后的代码等数据。方法区无法满足内存分配需求时,抛出OutOfMemoryError、JVM规范没要求这个区域需要实现垃圾收集,因为这个区域主要针对的是类和常量池的信息回收,回收结果往往难以令人满意
运行时常量池:是方法区的一部分。java语言不要求常量只能在编译器产生,换言之,在运行期间也能将新的常量池放入。

方法去空间大小设置: -XX:PremSize -XX:MaxPermSize
1.8之后设置: -XX:MetaspaceSize -XX:MaxMetaspaceSize

3.执行引擎

4.本地库接口

小结

类加载器将代码转换成字节,然后通过运行时数据区将字节码加载到内存中,执行引擎在内存中将字节码翻译成系统能够执行的命令,交由CPU执行而这个过程需要使用对应语言的本地库接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值