【JVM】 ---- 大白话图文之JVM类加载机制、内存区域、垃圾回收

JVM

一、类加载机制

JVM整体的运行原理:首先从".java"代码文件编译成".class"字节码文件,然后类加载器把".class"字节码文件中的类给加载到JVM中,接着JVM执行我们写好的那么类中的代码。

image-20210616170802579

1、JVM什么时候会加载一个类?

一个类从加载到使用,一般会经过下面这个过程:

加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

当代码中使用类的时候,就会加载一个类。

比如包含main()方法的主类在JVM进程启动之后被加载到内存(加载字节码文件),然后开始执行main()方法中的代码。

2、验证、准备、解析、初始化过程

image-20210616173100798

2.1 概念

  • 验证阶段:根据Java虚拟机规范,校验加载进来的".class"文件中的内容是否符合指定的规范。

  • 准备阶段:给类分配一定的内存空间,以及它里面的类变量(即static修饰的变量)分配内存空间,设置默认的初始值。(而实例变量在创建类的实例对象时才会初始化)

  • 解析阶段:将符号引用替换为直接引用

  • 初始化阶段(核心阶段):正式执行类初始化的代码,完成类变量的真正赋值操作。static静态代码块,也是在这个阶段完成的。

    (这个阶段主要是准备好类级别的数据,比如静态代码块,静态成员赋值,

    初始化跟对象无关,用new关键字才会构造出一个对象出来)

例子:

public class ReplicaManager {
   
	public static int flushInterval = Configuration.getInt("replica.flush.interval");
}

- 准备阶段:首先给ReplicaManager类分配一定的内存空间,然后给类变量flushInterval分配内存空间,设置0初始值
- 初始化阶段:`Configuration.getInt("replica.flush.interval") `完成一个配置项的读取,然后赋值给类变量`flushInterval`

2.2 什么时候初始化一个类?

  • 比如"new ReplicaManager()"实例化对象,就会触发类的加载到初始化过程,把这个类准备好,然后再实例化一个对象出来。
  • 包含"main()"方法的主类,必须是立马初始化的
  • 初始化一个类的时候,如果父类还没初始化,那么必须先初始化它的父类

类初始化时机:

  1. 当创建某个类的新实例时(如通过new或者反射、克隆、反序列化等)
  2. 当调用某个类的静态方法时
  3. 当使用某个类或者接口的静态字段时
  4. 调用Java API的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时
  5. 当初始化某个子类时
  6. 当虚拟机启动某个被标明为启动类的类

3、类加载器和双亲委派机制

3.1 类加载器

  • 启动类加载器Bootstrap ClassLoader

    负责加载机器上安装的Java目录下的核心类("lib"目录)

  • 扩展类加载器Extension ClassLoader

    负责加载"lib\ext"目录中的类

  • 应用程序类加载器Application ClassLoader

    负责加载"ClassPath"环境变量所指定的路径中的类,可以理解为自己写好的Java代码

  • 自定义类加载器

    根据自己的需求加载一些类

    (如何实现一个自定义类加载器?自己写一个类,继承ClassLoader类,重写类加载的方法)

3.2 双亲委派机制

启动类加载器位于最上层、扩展类加载器在第二层、应用程序类加载器在第三层、最后一层是自定义类加载器

image-20210616174108689

如果一个应用程序类加载器需要加载一个类,首先委派给自己的父类加载器去加载,最后传导到顶层的类加载器去加载,如果父类加载器在自己负责加载的范围内,没找到这个类,那么就下推加载权力给自己的子类加载器。

好处:

  • 每个层级的类加载器各司其职,不会重复加载一个类
  • 保护一些核心类的安全

3.3 Tomcat类加载机制

Tomcat本身就是用Java写的,它自己就是一个JVM,我们写好的那些系统程序,通过编译后的.class文件放入一个war包,然后在tomcat中运行。

image-20210616174729113
  • Tomcat自定义了Common、Catalina、Shared等类加载器,是用来加载Tomcat自己的一些核心基础类库的
  • Tomcat为每个部署在里面的Web应用都有一个对应的WebApp类加载器,负责加载我们部署的这个Web应用的类
  • Jsp类加载器,则是给每个JSP都准备了一个Jsp类加载器

每个WebApp负责加载自己对应的那个Web应用的class文件,即我们写好的系统打包好的war包中的所有class文件,不会传到给上层类加载器去加载。

image-20210616205849026

Shared底层细分了不同的web类加载器用于隔离不同的web项目,打破了双亲委派机制,由自定义类加载器先加载类。

3.3.1 破坏双亲委派

原因:隔离、灵活、性能

  1. 不同的项目依赖Spring不同的包,那么就会导致依赖冲突问题,如果用不同的加载器,就能起到隔离的作用

  2. 当需要增加或者减少单独的某个web项目的部署,用多个类加载器可以灵活的实现

  3. 用多个类加载器性能要比用一个类加载器性能要高

二、内存区域

JVM在运行我们写好的代码时,必须使用多块内存空间,不同的内存空间用来放不同的数据,然后配合我们写的代码流程,才能让我们的系统运行起来。

1、内存区域划分

  • 线程共享的区域
    • 方法区
    • 直接内存(非运行时数据区的一部分)
  • 线程私有的区域
    • 程序计数器
    • 虚拟机栈
    • 本地方法栈

如图是JDK1.8之前:

image-20210616175505134

如图是JDK1.8

image-20210616175540742

1.1 存放类的方法区

方法区在JDK1.8以前的版本,代表JVM中的一块区域。

主要是放从".class"文件里加载进来的类,还有一些类似常量池的东西也放在这个区域里。

JDK1.8以后,这块区域改成了"Metaspace",即元数据空间的意思,主要还是存放我们自己写的各种类相关的信息。

image-20210616175838057

1.2 执行代码指令用的程序计数器

我们编写的代码首会存在于".java"后缀的文件中,但是计算机是看不懂我们写的代码的,所以就得通过编译器,把".java"后缀的源文件编译成".class"后缀的字节码文件,

这份文件存放的就是我们写出来的代码编译好的字节码。

字节码指令对应了一条一条的机器指令,计算机只有读到这种机器码指令,才知道具体应该要干什么。比如字节码指令可能会让计算机从内存读取某个数据,或者把某个数据写入到内存里。

在执行字节码指令的时候,JVM需要一个特殊的内存区域,就是"程序计数器",用来记录当前执行的字节码指令的位置即记录目前执行到了哪一条字节码指令

image-20210616180734846

1.3 虚拟机机栈

Java代码在执行的时候,一定是线程来执行某个方法中的代码。在方法里,我们经常会一定一些方法内的局部变量。

因此,JVM必须有一块区域是用来保存每个方法内局部变量等数据的,这个区域就是Java虚拟机栈

每个线程都有自己的Java虚拟机栈,如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧

栈帧有这个方法的:局部变量表、操作数栈、动态链接、方法出口等信息。

例子:

public class ReplicaManager {
   

   public void loadReplicasFromDisk() {
   
      Boolean hasFinishedLoad = false;
      if(isLocalDataCorrupt()) {
   }
   }

   private Boolean isLocalDataCorrupt() {
   
      Boolean isCorrupt = false;
      return isCorrupt;
   }
}

整个过程如图所示:

image-20210616190408312

结合前面的知识,如图所示:

image-20210616190507458

1.4 Java堆内存

存放我们在代码中创建的各种对象,实例变量也是在堆内存的。

案例:

public class Kafka {
   
   public static void main(String[] args) {
   
      ReplicaManager replicaManager = new ReplicaManager();
      replicaManager.loadReplicasFromDisk();
   }
}

public class ReplicaManager {
   

	private long replicaCount;

	public void loadReplicasFromDisk() {
   
		Boolean hasFinishedLoad = false;
		if(isLocalDataCorrupt()) {
   }
	}

	private Boolean isLocalDataCorrupt() {
   
		Boolean isCorrupt &
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM中的双亲委派机制是一种类加载机制,它规定了在Java中一个类被加载时如何进行类加载器的选择。根据这个机制,当一个类需要被加载时,首先会由类加载器ClassLoader检查是否已经加载过该类,如果是,则直接返回已经加载过的类;如果不是,则将该请求委派给父类加载器去加载。这样的过程会一直向上委派,直到达到顶层的引导类加载器(Bootstrap ClassLoader)。引用 引用中提到,并不是所有的类加载器都采用双亲委派机制。Java虚拟机规范并没有强制要求使用双亲委派机制,只是建议使用。实际上,一些类加载器可能会采用不同的加载顺序,例如Tomcat服务器类加载器就是采用代理模式,首先尝试自己去加载某个类,如果找不到再代理给父类加载器。 引用中提到,引导类加载器(Bootstrap ClassLoader)是最早开始工作的类加载器,负责加载JVM的核心类库,例如java.lang.*包中的类。这些类在JVM启动时就已经被加载到内存中。 综上所述,JVM的双亲委派机制是一种类加载机制,它通过类加载器的委派方式来加载类,首先检查是否已经加载过该类,如果没有则委派给父类加载器去加载,直到达到顶层的引导类加载器。不过,并不是所有的类加载器都采用该机制,一些类加载器可能会采用不同的加载顺序。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [JVM-双亲委派机制](https://blog.csdn.net/m0_51608444/article/details/125835862)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [jvm-双亲委派机制](https://blog.csdn.net/y08144013/article/details/130724858)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值