JVM (一)之类加载器+运行时数据区+双亲委派

前言

做一个有趣的程序员。哈哈哈哈

本次铁村的小蓝猫主要给大家详细分享JVM其中重要的2块。 类装载器 +运行时数据区。

分享前,我们先了解下本次分享内容的框架。

在这里插入图片描述

一、JVM定义

1.1JVM是什么

jvm是java的核心和基础,在java编译器和os平台之间的虚拟处理器,可在上面执行字节码程序。

1.2JVM由什么组成

JVM 包含二个子系统,和二个组件分别为
1.类加载器

2.执行引擎

3.运行时内存区域

4.本地接口 (基本上用不到)

一个字节码文件,通过类装载子系统,丢到运行时数据区,最后通过字节码执行引擎来执行内存区中的代码。

在这里插入图片描述

二、类加载器

类加载器脑图

在这里插入图片描述

2.1 Java命令执行代码流程

1.java指令调用jvm.dll文件,创建java虚拟机

2.启动java虚拟机过程中,创建引导类加载器

3.通过c++调用java虚拟机的真正的启动程序sum.misc.Lancher。这个类调用launcer.getClassLoader()可以创建java的其他类加载器

  1. 通过jvm的类加载器,调用loadClass方法加载磁盘里的.class字节码文件

5.加载完成字节码文件后,调用到c++的Main方法,执行代码

6.JVM摧毁

java命令执行代码流程

2.2 类加载过程

类加载的过程可分:

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

1.加载:把class字节码文件从各个来源通过类加载器加载到内存中(遵循双亲委派机制)

2.验证:检查字节流文件是否符合JVM规范

3.准备:为类中定义的变量分配内存和赋值初始化–比如int=0;boolean=false,引用=null,由虚拟机规定,和最终值无关

4.解析:将java虚拟机常量池的符号引用替换为直接引用

5.初始化:对static修饰的静态变量或者代码赋值–比如int从0变为1

6.使用

7.卸载

2.3 类加载器种类

1.引导类加载器(bootstrapLoader)
负责加载支撑JVM运行JRE的lib目录核心库

2.扩展类加载器(extClassLoader)
负责加载支撑JVM运行JRE的lib目录下ext扩展目录中的JAR包

3.应用程序加载器(appClassLoader)
负责ClassPath,主要负责自己写的那些类

4.自定义加载器
继承ClassLoader,核心方法,负责加载用户自定义路径下的类包

2.4 双亲委派机制

2.4.1双亲委派机制概括

加载某个类时会先委托父加载器寻找目标类。

找不到再委托上层父加载器加载。

如果所有父加载器都在自己的加载类路径下找不到目标。

则在自己的类加载路径中查找并载入目标类。

2.4.2双亲委派流程图解

在这里插入图片描述

2.4.3双亲委派机制的好处

1.沙箱安全机制-防止核心API被随意篡改

2.避免类的重复加载-保证加载类的唯一性

2.4.4怎么打破双亲委派机制

自定义类加载器,重写类加载方法,

实现自己的加载逻辑,不委派给父加载器加载。

注意,因为java核心类还得是靠双亲委派机制来加载,

所以得加上相关的判断逻辑使核心类继续使用双亲委派机制加载.

以下自定义加载器是继承ClassLoader,重写的2个方法的代码

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] data = new byte[0];
            try {
                data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized (getClassLoadingLock(name)) {
                Class<?> c = findLoadedClass(name)
                if (c == null) {
                    long t1 = System.nanoTime();
                    if(!name.startsWith("com.attcatstudy.jvm")){
                        c = this.getParent().loadClass(name);//非这个包其他都还是向上委派
                    }else {
                        c = findClass(name);
                    }
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();

                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }

        }
    }

贴心小例子:
任务目标:一个main方法 ,实现运行2个不同路径,相同包名,加相同类名的类

准备2 个user,分别丢在main 方法根目录+自定义目录

根目录user:
存放
在这里插入图片描述
电脑路径
在这里插入图片描述

package com.attcatstudy.jvm;
/**
 * @author attcat
 * ---做一个有趣的程序员
 * @date 2022-10-01 18:41
 * @description
 */
public class User {
    public String a;
    static {System.out.println("*************load Use里面************"); }
    public void  sout(){
        System.out.println("*************load sout Use里面************");
    }
}

自定义user:

存放
在这里插入图片描述

package com.attcatstudy.jvm;
/**
 * @author attcat
 * ---做一个有趣的程序员
 * @date 2022-10-01 18:41
 * @description
 */
public class User {
    public String a;
    static {System.out.println("*************load Use外面************"); }
    public void  sout(){
        System.out.println("*************load sout Use外面************");
    }
}

运行main方法()

public static void main(String[]args)throws Exception{
     //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加器设置为应用程序类加载器AppClassLoader
        User user=new User();
        user.sout();

      //选择自己文件的根目录地址
        MyClassLoader classLoader=new MyClassLoader("/Users/attcat/studyCode/");
        //创建 /Users/attcat/studyCode/ 几级目录,将User类的复制类User1.class丢入该目录
        Class clazz=classLoader.loadClass("com.attcatstudy.jvm.User");
        Object obj=clazz.newInstance();
        //调用sout方法
        Method method=clazz.getDeclaredMethod("sout",null);
        method.invoke(obj,null);
        System.out.println(clazz.getClassLoader().getClass().getName());
        }
        }

运行结果达到目的。2个user同时被加载 并且执行
在这里插入图片描述

2.4.5tomcat为什么要打破双亲委派机制

1.一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,
不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离

2.部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机

3.web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。

4.web容器要支持jsp的修改,jsp 文件最终也是要编译成class文件才能在虚拟机中运行, web容器需要支持 jsp 修改后不用重启。

2.4.6tomcat怎么实现热加载机制

每个jsp文件都有独立的类加载器

有线程去检测文件是否改动

如果改动了,就将这个类加载器置空,然后创建新的加载器来重新加载这个文件

三、运行时数据区

JVM运行时数据区主要分为:堆、栈、方法区(元空间)、本地方法栈、程序计数器。

其中,堆、方法区是所有线程共享。

程序计数器,本地方法栈、虚拟机栈、是每个线程独有的。

3.1 内容脑图+区域图

在这里插入图片描述

在这里插入图片描述

3.2 程序计数器

每一个线程独有,每个线程都有的一块内存空间,用于记录即将执行的代码的内存地址。

每一个方法执行完一行代码,字节码执行引擎会马上动态地修改程序计数器的值。根据这个值判断下一次执行那一个方法。

3.3 本地方法栈

每一个线程都有自己的本地方法栈,存放 用native修饰的本地方法

3.4 虚拟机栈

虚拟机栈:存放栈帧,先进后出。每次都从栈顶取,符合java代码的执行流程,后调用的方法先释放掉(出栈)

3.5 堆

所有线程共享的内存区域,唯一目的就是存放对象实例。

3.6 方法区

各个线程共享的内存区域,存储已被虚拟机加载的 类型信息、常量、静态变量、类信息等。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值