类加载学习笔记

一直想学习java虚拟机类加载机制,废话不多说,正片走起。

 

类加载机制:JVM将class文件加载到内存,并对数据进行校验,解析和初始化最终形成JVM可直接使用的java类型的过程。

 

 

 

1.类的生命周期

 

 

 

        类从被加载到虚拟机内存到卸载出内存共经历以下生命周期。

 

        加载,链接(包括:验证,准备,解析),初始化,使用,卸载。

 

 

 

        加载,验证,准备,初始化,卸载这五个运行顺序是固定的,而解析则不一定,某些情况下解析可以在初始化之后,这是为

 

了支持java运行时绑定。

 

 

 

2.生命周期简介

 

 

 

        2.1 加载:(1)通过类的全限定名获取定义类的二进制字节流。

 

                         (2)将二进制字节流所代表的静态存储结构转化为JVM方法区运行时数据结构

 

                         (3)在堆中生成一个代表此类的java.lang.Class对象,作为这个类的访问入口

 

 

 

        2.2验证:

 

 

 

                         保证Class文件字节流的信息符合JVM规范,并且不会危害虚拟机自身安全。(包括文件格式验证,元数据验证,

 

字节码验证,符号引用验证)

 

 

 

                           文件格式验证:验证字节流是否符合Class文件格式的规范并且能被当前版本的JVM处理。文件格式验证后,字

 

节流才会进入方法区进行存储。所以以下三个验证都是基于方法区存储结构进行的。

 

 

 

                           元数据验证:验证字节码描述的信息进行语义分析,保证他符合java语言的规范。(是否有父类,是否重写了父

 

类的final方法,是否实现了抽象父类的所有方法,是否定义了与父类冲突的方法)

 

 

 

                           字节码验证:进行数据流和控制流的分析,对类的方法体进行校验分析,保证程序运行时不会出现危害JVM安全

 

的行为

 

                           符号引用验证:此校验发生在JVM将符号引用转化为直接引用的时候(解析阶段),可以看作对类自身以外的信

 

息进行匹配性校验。

 

 

 

        2.3准备

 

 

 

                    准备阶段是正式为类变量(static变量)分配内存(方法区内存)并设置初始值。

 

                    例如:public static int i=1;

 

                    那么准备阶段过后i的值为0而不是1。因为此时并未执行任何java方法,而赋值动作的putstatic指令发生在程序编译过

 

后存放在<clinit>方法之中,所以赋值动作是在初始化时发生。

 

                    但如果是:public static final int i=1;

 

                    编译时javac会为value生成ConstantValue属性,准备阶段JVM就会根据ConstantValue的设置为i赋值为1。

 

 

 

       2.4解析

 

 

 

                     解析阶段将常量池里的符号引用(对目标的描述,例如我的老板)替换为直接引用(定位到内存的指针,比如我左

 

边10米的地方)。

 

 

 

 

 

       2.5初始化:

 

 

 

                      初始化是执行类构造器 <clinit>方法的过程。

 

                      <clinit>是编译器自动收集类变量的赋值动作和static代码块中的语句合并而成的。虚拟机能够保证 <clinit>在多线程

 

环境下被正确的加锁和同步。

 

 

 

 

 

                     虚拟机严格规定了有且只有四种情况必须对类进行初始化。

 

                   (1)当遇到new (创建对象),getstatic(获取静态变量) ,pustatic(设置静态变量),invokestatic

(调用静态方法)这四条字节码指令时,如果类没有进行初始化,则触发类的初始化。

 

                   (2)当java.lang.reflect包的方法反射调用一个类的时候,如果类没有被初始化,则触发其初始化。

 

                   (3)初始化一个类的时候如果他的父类没有被初始化,则先初始化他的父类。

 

                   (4)虚拟机启动时用户需要指定一个执行的主类(含有main方法),虚拟机会先初始化这个主类。

 

     

 

        以下过程不会触发类的初始化:

 

                  (1)当访问一个静态域(静态变量)时,只有定义这个静态域的类会被初始化。(子类访问父类的静态变量不会初始化子类)

 

                  (2)通过数组定义类的引用,不会触发此类的初始化。

 

                  (3)引用常量不会触发类的初始化(static final定义的常量)。

 

 

 

3.类加载器

 

              实现“通过一个类的全限定名获取到描述此类的二进制字节流”的动作。

 

 

 

              类加载器的层次结构: 

 

                              启动类加载器(bootstrap class loader):用来加载java核心库,比如java.lang.String,java.lang.Class;他不是用

 

java语言编写的也不继承自java.lang.ClassLoader。

 

                              扩展类加载器(extension class loader):用来加载java的扩展哭库(JAVA_HOME/jre/lib/rt.jar或者jav.ext.dirs路径下的内容)

 

                              应用类加载器(application class loader):一般来说java应用的类都是由他来加载的。

 

                              自定义类加载器(bootstrap class loader):通过继承java.lang.ClassLoader实现自己的类加载器。

 

 

 

自定义类加载器

 

package com.lwx.study;

 

import java.io.*;

 

public class FileClassLoader extends ClassLoader{

    //class文件的根目录

    private String rootDir;

 

    public FileClassLoader(String rootDir) {

        this.rootDir = rootDir;

    }

 

    @Override

    protected Class<?> findClass(String s) throws ClassNotFoundException {

        Class<?> x = findLoadedClass(s);

        

        //如果类已经被加载过直接返回

        if (x!=null){

            return x;

        }

        ClassLoader parent = this.getParent();

        

        //如果类没有加载先交给父加载器加载,依次往上提交  

        AppClassLoader  ExtClassLoader BootStrapClassLoader

        try {

            x= parent.loadClass(s);

        } catch (ClassNotFoundException e) {

            //e.printStackTrace();

        }

        if (x!=null){

            return x;

        }

        //父加载器不能加载,则自己加载

        byte[]by=getClassData(s);

        x = this.defineClass(s, by, 0, by.length);

       //自己不能加载则抛出异常       

        if (x==null){

            try {

                throw  new FileNotFoundException();

            } catch (FileNotFoundException e) {

                e.printStackTrace();

            }

        }

        return x;

    }

    private byte[] getClassData(String s){

        String path=rootDir+"/"+s.replace(".","/")+".class";

        InputStream is=null;

        ByteArrayOutputStream baos=null;

        try {

            is=new FileInputStream(path);

            byte[]bytes=new byte[1024];

            baos=new ByteArrayOutputStream();

            int temp;

            while((temp=is.read(bytes))!=-1){

                baos.write(bytes,0,temp);

            }

            return baos.toByteArray();

        } catch (Exception e) {

            e.printStackTrace();

        }finally {

            try {

                if (baos!=null)

                    baos.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

 

            try {

                if (is!=null)

                    is.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

        return null;

    }

}

双亲委托机制

 

                当一个类加载器收到类加载请求时,它不会去尝试加载,而是委托给父类加载器,只有当父类加载器不能完成加载动作时,它才回去尝试自己加载。

 

                好处是,如果启动类加载器可以加载这个类,那么子类就没有机会加载这个类。例如用户定义了一个java.lang.String

 

类,他会一层层往父类加载器委托,最终到启动类加载器(加载核心库)。启动类加载器发现自己能加载java.lang.String,那么

 

他就会去加载java.lang.String。这样用户永远无法定义与核心库冲突的全限定类名。

 

 

 

破坏双亲委托机制

 

             (1)有些场景下双亲委托机制无法满足我们的需求,如果基础类要调用用户的代码,首先基础类要通过ExtClassLoader加

 

载,但是ExtClassLoader却无法加载用户代码,这个时候我们可以选择不使用双亲委托机制。

 

                例如线程上下文类加载器(默认为AppClassLoader):

 

                                 Thread.currentThread().getContextClassLoader()

 

                                 //通过设置改变类加载器

 

                                 Thread.currentThread().setContextClassLoader(ClassLoader classLoader);

 

 

 

            (2)Tomcat服务器的类加载机制也不能使用系统默认的类加载器,它为每个应用提供了一个独立的类加载器,而且不是

 

采用双亲委派机制,而是子类加载器优先加载。

 

 

 

             (3)OSGI,面向java的动态模块系统,每个模块有自己对应的类加载器,他负责加载模块包含的java包和类。比如A模

 

块引用B模块的Test类时,加载Test类的类加载器是B模块的加载器。(谁定义谁加载)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值