Class加载-初始化-Loading

详解Class加载-初始化-Loading

类被加载到虚拟机内存中开始,到卸载出内存为止,整个生命周期包括7个阶段:
加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Usting)、卸载(Unloading)
在这里插入图片描述

加载Loading

  1. 通过一个类的全限定名来获取类的二进制字节流
    并未指定总哪获取,怎么获取。所以字节流可以是存储在硬盘上的文件,可以是运行时动态生成的二进制字节流,可以是有其他文件生成的(JSP对应的class文件)等等
  2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的Class对象,作为方法区这个类的访问入口

类加载器

所有的class都是被类加载器(ClassLoader)加载到内存的。
每一个类,都需要它的类加载器和自身来确定在JVM中的唯一性。
每个类加载器都有自己维护的类名称空间
在判断两个类是否相等时,只有这两个类由同一个类加载器加载的前提下才有意义

启动类加载器(BootStrap ClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(Appliaction ClassLoader)
自定义类加载器

在这里插入图片描述
类加载器之间的层次关系,被称为双亲委派模型,一般采用组合关系来复用父加载器的代码

双亲委派模型

要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。

双亲委派模型的工作过程
类加载器收到类加载请求时,首先不会自己加载这个类,而是把这个请求委派给父类加载器去完成。所以所有的类加载请求都会传递到顶层的启动类加载器(BootStrap)中,只有父加载器无法完成加载请求时(加载器负责的加载范围中无法通过类的全限定名找到此类),子加载器才会尝试进行加载。

ClassLoader源码
findCache →parent.loadClass() → findClass()


protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过了(每个类加载器都有自己维护的类名称空间)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException 
                    // 说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    // 如果仍未找到,则按顺序调用findClass
                    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;
        }
    }

在这里插入图片描述

1. 从子到父,再从父到子的过程

   2. 双亲委派模型对保证JAVA程序的稳定运作非常重要。
   		如果随意一个class文件能被任意加载器加载会导致系统中出现非常多的名字相同的类,会使得java类型体系中最基础的行为无法保证。

      ​		如Object类,java.lang.String 类由自定义类加载器加载。

   4. 父加载器 

      ​		父加载器不是“类加载器的加载器”,也不是“类加载器的父类加载器”

      ​		APP(应用程序类加载器)的加载器是BootStrap(启动类加载器),但它的父加载器是Extension(扩展类加载器)

      ​		从源码角度看。类加载器代码中,有一个parent对象,这个对象指定的加载器是谁,则这个类的父加载器就是谁

自定义类加载器

  1. 什么时候需要使用到自定义的类加载器

      ​	加载自己指定目录下的class文件时。

      ​	热部署,动态加载class文件(网络传输的二进制流),java代码存储在数据库中时。

      ​	写框架写类库时都需要用到自定义的类加载器。如:spring,tomcat都有自定义的类加载器。

   2. 自定义类加载器的实现

      ​	主要在于自定义实现findClass(name)方法。(设计模式:构造函数,模版方法)
/**
 * Created by 刘绍 on 2020/2/2.
 */
public class MyClassLoader extends ClassLoader{

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        File f = new File("c:/test/",name.replace(".","/").concat(".class"));

        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            //将文件流读取进字节数组中
            while ((b = fis.read()) != 0) {
                baos.write(b);
            }

            //转成二进制字节数组
            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();

            //defineClass 将二进制流转成class对象
            return defineClass(name,bytes,0,bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception{
        ClassLoader l = new MyClassLoader();
        Class<?> aClass = l.loadClass("com.ls.jvm.LoadClassByHand01");

        System.out.println(aClass);
    }
}

lazyloading(lazyInitializing)

   1. JVM并未规定什么时候加载class文件

   2. 但是严格规定了什么时候需要进行初始化(Initializing)**五种情况**

      1. new、getstatic、putstatic、invokestatic指令时(final变量除外)

         这4条指令最常见的java代码:

         - ​	使用new关键字实例化对象的时候

         - ​	读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候

         - ​	调用一个类的静态方法的时候。

      2. java.lang.reflect对类进行反射调用的时候

      3. 初始化子类的时候,父类必须初始化

      4. 当虚拟机启动时,被执行的主类必须初始化(main()方法的那个类)

      5. 使用动态语言支持java.lang.invoke.MethodHandle解析结果为REF_getStatic、REP_pubStatic、REF_invokeStatic的方法句柄,该类必须初始化

混合模式

  解释器	bytecode intepreter

  JIT即时编译器  just in-Time compiler

  

  JVM默认使用混合使用解释器 + 热点代码编译

  **不是所有的代码都会被JIT进行即时编译的,如果是这样的话JAVA就变成完全不能跨平台了,jit会把你的代码编译成与平台相关的底层机器语言,就失去了跨平台能力**

  1. 起始阶段采用解释执行
  2. 热点代码检测 (-XX:CompileThreshold = 10000)
     - 多次被调用的方法(方法计数器:监测方法执行频率)
     - 多次被调用的循环(循环计数器:监测循环执行频率)
     - 达到一定频率时,代码被编译成本地方法(Native)


  -Xmixed 默认混合模式(开始解释执行,启动速度较快,对热点代码实行检测和编译)

  -Xint 使用解释默认,使用解释模式,启动很快,执行稍慢

  -Xcomp 使用纯编译模式,执行很快,当类文件很多的时候启动非常慢
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值