[JVM学习]双亲委派机制及PC寄存器(程序计数器)

双亲委派机制

  • Java虚拟机对class文件的加载采用的是按需加载,也就是说需要使用该类时才会将他的class文件加载到内存生成class对象,而且加载到某个类的class文件时,Java虚拟机采用的是双亲委派机制,他把任务交给父类来进行处理,这是一种任务委派模式

工作原理

  1. 如果一个类加载收到了类加载请求,它并不会自己先去加载而是把这个请求委托给父类的加载器去执行
  2. 如果父类的加载器还存在存在其父类加载器则进一步向上委托,依次向上递归,请求最终将达到的启动类加载器
  3. 如果父类加载器可以完成类加载任务,就成功返回,若父类加载器无法完成此加载任务,子类加载器才会尝试自己去加载,这就是双亲委派机制
    在这里插入图片描述

查看源码

  • 在java.lang.ClassLoader.java下查看加载类的方法(loadClass)
 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检测当前类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // c == null 代表当前类没有被加载过
                long t0 = System.nanoTime();
                try {
                    // 如果有父类加载器就让父类加载器进行加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果父类加载器为空,表示已经到启动类加载器(BootStrapClassLoader)了
                        // 启动类加载器(BootStrapClassLoader)是由C/C++实现的,通过get是无法获取到的
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果从非空的父类加载器中找不到类,则抛出ClassNotFoundException异常
                }
                if (c == null) {
                    // 如果仍然没有被加载,则尝试自己去加载该类
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • 从上述的代码中可以看出,当一个类加载时,不考虑用户自定义的类加载器,类首先会在系统类加载器(AppClassLoader)中检查是否已经被加载,如果不用加载。如果没有,那么会拿到父加载器,然后调用父加载器扩展类加载器(ExtClassLoaderloadClass)的loadClass。父类中同理也会先检查自己是否已经加载过,如果没有再往上到启动类加载器(BootstrapClassLoader)。在到达启动类加载器(BootstrapClassLoader)之前,都是在检查是否加载过,并不会选择自己去加载。直到启动类加载器BootstrapClassLoader,没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

双亲委派机制的优势

  • 避免类的重复加载
  • 保护程序安全,防止核心API被篡改
    • 自定义类:java.lang.String
  • 例子
    自定义String类,但在加载自定义String类的时候会率先使用应到类的加载器加载,而引导类加载器在加载的过程中会先加载JDK自带的文件(rt.jar包中的java\lang\String.class),此时不在加载自定义的String类.报错信息说没有main方法,就是因为加载的是rt.jar包中的java.lang.String类中是没有main方法的.所以就会抛出异常。这样可以保证对java核心源代码的保护,这就是沙箱安全机制
package java.lang;

public class String {
    // 初始化的第三阶段初始化
    static{
        System.out.println("自定义string");
    }
//    错误: 在类 java.lang.String 中找不到 main 方法,
    public static void main(String[] args) {
        System.out.println("hello,String");
    }
}
/**
     * 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
     *    public static void main(String[] args)
     * 否则 JavaFX 应用程序类必须扩展javafx.application.Application
     * @param args
     */

Java判断是否是同一个类

  • 在JVM中表示两个class对象是否是同一个类存在两个必要条件
    • 类的完整类名必须一致,包括包名
    • 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
  • 换句话说,在JVM中及时这两个类对象(class对象)来源于同一个Class文件,被同一个虚拟机所加载,但只要是加载他们的ClassLoader实例对象不同,那么这两个类对象也是不相等的

PC寄存器(程序计数器)

  • PC寄存器位于运行时数据区,PC寄存器为线程私有的

  • Jvm中的程序计数寄存器(Program Count Register),是对物理PC寄存器的一种抽象模拟

  • 作用:PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎读取下一条指令
    在这里插入图片描述

  • 他是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域

  • 在JVM规范中,他的每个线程都有他自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致

  • 任何一个时间线程都只有一个方法在执行,也就是所谓的**当前方法,**程序计数器会存储当前线程正在执行的Java方法的Jvm指令地址,或者如果是在执行native方法,则是未指定值(undefined)

  • 他是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

  • 字节码解释器工作时,就是通过改变这个技术器的值来选取下一条需要执行的字节码指令

  • 它是唯一一个在Java虚拟机规范中没有规定任何OutotMemoryError情况的区域
    在这里插入图片描述

  • 使用PC寄存器存储字节码指令地址的作用(记录当前线程的执行地址的作用)

    • 因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始执行
    • JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条该执行什么样的字节码指令
  • PC寄存器(程序寄存器)为什么设为线程私有

    • 为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法是为每一个线程都分配一个PC寄存器,这样一来各个线程便可以独立计算,从而不会出现互相干扰的情况
    • 由于CPU时间片轮的限制,中多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值