JVM 浅学

JVM

文章目录

  • JVM
  • 内存结构
    • JVM执行流程
    • 堆内存
    • 方法区
    • java 虚拟机栈
    • 本地方法栈
    • 程序计数器
    • 运行时常量池
    • 直接内存
  • JVM 类加载
    • 双亲委派模型
    • 链接
    • 初始化
  • 底部

内存结构

设置内存空间大小

  • -Xms 堆内存最小空间大小
  • -Xmx 堆内存最大空间大小
  • -XX:NewSize 设置新生代最小空间大小
  • -XX:MaxNewSize 设置新生代最大空间大小
  • -XX:PermSize 设置永久代(方法区)最小空间大小
  • -XX:MaxPermSize 设置永久代(方法区)最大空间大小
  • -Xss 设置每个线程的堆栈大小

JVM 浅学_第1张图片

  • Class文件 : 编译成字节码的java文件,Class文件才是JVM可以识别的文件。
  • Class Loader 类加载器: 从文件系统,或者网络中加载class信息,并与运行时数据区进行交互
  • Runtime Data Area 运行时数据区: 主要包含五个模块
    • Method Area方法区 :所有的类级别数据存储在这里,静态变量等,方法区线程共享
    • Heap Atra 堆区域: 所有的对象实例,变量等。线程共享
    • Stack Area 栈区: 每个线程单独创建。
    • PC Registers 寄存器/计数器 : 保存当前执行指令的地址,为多线程切换记录执行 位置
    • Native Method stacks 本地方法栈: 保存本地方法的信息,每个线程单独创建
  • Execution Engine 执行引擎: 分配给运行时数据区字节码将由执行引擎执行,执行引擎读取字节码文件并逐个执行。垃圾回收器时执行引擎的一部分
  • Native InterFace 本地接口: 本机方法库进行交互,并提供执行引擎所需要的本机库
  • Native Libraries 本地库 : 执行引擎所需要的本地库的集合

JVM执行流程

JVM 浅学_第2张图片

  • 步骤 1 : 我们的 Demo.java 文件,通过 JDK 的 javac 命令,成功的被编译成为额 Demo.class 文件;
  • 步骤 2 :JVM 有自己的类加载器,将编译好的 Demo.class文件进行了加载;
  • 步骤 3 :类加载器将加载的 Demo.class文件投放到了运行时数据区,供程序执行使用;
  • 步骤 4 :运行时数据区将字节码文件,交给执行引擎执行;
  • 步骤 5 :执行引擎执行完毕,会对运行时数据区的数据进行操作,比如说垃圾回收机制是执行引擎的一部分,垃圾回收机制,针对的是运行时数据区的堆空间,后续我们会详细讲解;
  • 步骤 R :我们发现图中有很多步骤 R ,此处 R 代表 Random,即随机发生的步骤。其实就是我们在执行过程中的一个本地方法的调用,只要我们的程序在运行过程中需要调用本地方法,那么步骤R就会发生。

堆内存

堆内存(Heap)是java虚拟机管理内存最大的一块,各个线程之间共享,在虚拟机启动时创建,此区域唯一目的就是存放实例对象几乎所有的实例都是在这里分配内存。堆内存时垃圾收集器(GC)管理的主要区域。

垃圾手机器采用分代收集算法,堆内存被分为新生代、老年代新生代内存又被分为(Eden 空间、From Survivor 空间 、To Survivor 空间)。java虚拟机规范规定,堆内存可以在物理不连续的空间上、只要逻辑上时连续的即可。堆内存中没有足够的内存来完成实例分配,并且也无法在扩展时,将会抛出 OutOfMemoryError 异常

方法区

方法区(Method Area) 包含了类信息、静态变量、常量、常量池,是各个线程共享的内存区域。它存储已被虚拟机加载的类信息、静态变量、常量、常量池即编译后的代码等数据。为了与java堆区分,方法区还有一个别名 Non-Heap(非堆)。(也有些地方把方法区叫做 永久代 Permanent Generation)。这个区域的内存回收目标主要是针对常量池的回收和堆类型的卸载, 一般来说这个区域的回收"成绩"比较满意,尤其是类型卸载,条件相当苛刻,但是这部分区域的回收确实有必要的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。方法区中的常量和静态变量引用的对象,可作为 CG Root。

java 虚拟机栈

java(Java Virtual Machine Stacks) ,线程私有的,生命周期和线程相同。每个方法执行的同时,会创建一个栈帧(Stacks Frame)用于存储局部变量表、操作数栈、动态链接、方法出口信息等。方法的执行,对应栈帧在虚拟机中入栈道出栈的过程( 创建栈帧执行方法,程序计数器会指向栈顶 )。局部变量表存放了编译期可知道的各种基本数据类型、对象引用(Reference 类型,它不等同与对象本身,根据不同的虚拟机实现,它可能是一个指向对象启始地址的引用指针,可能指向一个代表对象句柄或者与此对象相关的位置)

本地方法栈

本地方法栈(Native MetHod Stack) 与java虚拟级栈发挥的作用相似,他们之间的区别不过是java虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用的Native方法服务。java虚拟机桂发中堆本地方法栈中的方法使用的语言、使用方式与数据并没有强制规定,因此具体的虚拟机可以自由实现它。

程序计数器

程序计数器(program Counter Register)是一块几较小的内存空间,它的作用可以看作是当前线程锁执行的字节码的信号指示器,即保证线程切换后恢复到正确的执行位置。在虚拟机的概念模型里,字节码解析器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程回复等基础功能都需要依赖这个计数器来完成。由于java虚拟机的多线程时通过线程切换并获取时间片的方式来实现的,在任何一个确定的时刻,一个处理器(对于多喝处理来说是一个内核)只会执行一条线程中的指令。因此为了线程切换后能正确的执行道位置,每条线程都需要有一个对立的程序计数器,各个线程之间的计数器互不影响,独立存储,这一般称为线程私有内存,如果线程执行的是一个java方法,这歌计数器记录的是正在执行虚拟机字节码指令的地址。如果线程正在执行的是Natvie 方法,这个计数器则为空(Undefined),从内存区域是为一个在java虚拟机规范中没有规定任何OutOfMemoryError异常情况的区域

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放变异期生成的各种字面量和符号引用。这部分内容在类加载后进入方法区的运行时常量池存放。运行时常量池另一个重要特征就是具有动态性。java语言并不要求常量一定只有编译期才能产生,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的比较多就是String类的intern()方法

直接内存

直接内存(Direct Memory) 并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,而且叶可能导致OutOfMemeryError异常。在JDK1。4中新加入的NIO类,引入了一种基于通道(Channel)与缓存区(Buffer)的I/O方式。它可以使用Native函数库直接分配堆外内存,让后通过一个存储在java堆中的DirectByBuffer对象作为内存的引用进行操作。这样能在一些场景中提高性能,因为避免了java堆和Native堆中来回复制数据。值得注意的是,本机直接内存的分配不会收到java堆大小的限制,但会受到本季总内存限制,这可能导致各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常

JVM 类加载

启动 Bootstrap 、扩展 Extension 、 系统 System Appliaction

  • BootStrap 引导类加载器/根类加载器

该加载器加载的类库大多数都是 %JAVA_HOME%lib下面的核心类库。引导类涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,不允许直接通过引用进行操作。

  • 扩展 Extension

由Sun公司提供的ExtClassLoader(sun.misc;Launcher$ExtClassLoader)实现的,它负责%JAVA_HOME%/lib/ext或者少数由系统变量 -Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

  • 系统 System Application

由sun 公司提供的AppLcassLoader(sum.misc.Launcher$AppClassLoader)实现的,它负责将用户类路径(java-classpath或 -Djava.class.path变量所指的目录,即当前类所在的路径及其引用的第三方类库的路径)下的类库加载到内存中。开发者可以直接使用系统类加载器

双亲委派模型

JVM 浅学_第3张图片

向上委托: 一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类加载器区执行,如果父类加载器还存在其他父类加载器一次递归,请求最终达到顶层的启动类加载器。如果父类加载器可以完成任务,就成功返回

向下委托: 父类加载器无法完成此任务,子类加载器才会自己去尝试加载。

例子:

  1. 加载 /jre/lib/resources.jar (此类是需要被 BootStrap 加载的核心类库)

    .jar ——> 自定义加载器 ——> 系统类加载器 ——>扩展类加载器 ——> 启动类加载器(完成加载)

  2. 加载 /jre/lib/ext/cldrdata.jar (此类是需要被扩展类 Extension 加载的类库)

    .jar ——> 自定义加载器 ——> 系统类加载器 ——> 扩展类加载器(自己不加载,向上委派) ——> 启动类加载器 (自己无法创建向下委派)——> 扩展类加载器

链接

JVM 浅学_第4张图片

  • 验证 verify

为了确保Class 文件的字节流中包含的信息符合当前虚拟机要求,并不会危害虚拟机自身安全。验证过程的主要验证信息:验证过程中,主要对三种类型的数据进行验证,分别是"元数据验证、字节码验证、符号引用验证"

  1. 元数据验证:
    1. 验证这个类是否有父类
    2. 验证这个类是否继承了 final 修饰的类
    3. 这个类不是抽象类,是否实现了其父类或者接口中所需求的所有方法
    4. 验证类中的字段、方法、与父类的冲突(覆盖了父类的final字段、不合规则的重载)
  2. 字节码验证: 主要通过数据流和控制流分析,确保程序语义合法、符合逻辑。这歌阶段将对类的方法体进行校验分析,保证校验类的方法在运行时不会产生危害虚拟机安全事件
    1. 保证任意时刻操作数栈道数据类型与指令代码序列都能配合工作。例如不会出现类似这样的情况:在操作数据栈放置一个int类型的数据,使用时却按long类型来加载入本地栈变量
    2. 保证跳转指令不会跳转到方法体以外的字节码指令上
    3. 保证方法体中的类型转换时有效的,例如把一个子类对象赋值给父类数据类型,但是父类对象赋值给子类数据类型,或者一些没有关联的对象之间转换,是严重不会合法的。
  3. 符号引用验证: 符号引用验证可以看作是类对自己身以外(常量池中的各种符号引用)的信息进行批评校验
    1. 符号引用中通过字符串描述的全限定名是否能够找到对应的类
    2. 在指定类中是否存在符合方法的字段描述符以及简单名称锁描述的方法和字段
    3. 符号引用中的类、字段、方法的访问性(public 、default、protected、private) 是否可以被当前类访问
  • 准备 prepare

准备阶段正式为类变量分配内存并设置类变量默认值(通常情况下是数据类型的零值),这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被sataic修饰的变量),而不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在java堆中

int、0、long、0L、short、char、‘\u0000‘、byte、0、boolean、false、folat、0.0d、regerence、null

  • 解析 resolve
  1. 符号引用(Symbolic References) : 符号引用以一组符号来描述锁引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定义目标即可
  2. 直接引用(Direct References) 直接引用可以时直接指向目标的指针、相对便宜量或事一个能间接定位到目标的句柄。如果有了直接引用,那么引用的目标一定是已经存在与内存中。

解析过程具体的解析内容类或接口的解析、字段解析、类方法解析、接口方法解析

初始化

JVM 浅学_第5张图片

底部

你可能感兴趣的:(java,java,开发语言)