java之类加载

一、类的加载概念

类的加载是指java文件被编译器编辑为class文件之后,需要被加载到jvm中去的这个过程,叫做类加载。用什么来把类加载到内存中去呢?那就是类加载器。整个过程呢,大概就是把类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构

二、类的加载时期

类的加载是在程序的运行期间执行的,也不是说要用到的时候才会加载,可以进行类的预加载,而出现错误的提示,是在第一次用到这个类的时候才会出现。
生命周期:加载->验证->准备->解析->初始化

其中验证->准备->解析也叫链接过程

加载
1、通过类的全限定名来获取类的二进制字节流,用户可以自定义类加载器
2、将这个字节流所代表的静态存储结构转化为在方法区的运行时数据结构。
3、在内存中生成代表这个类的Java.lang.class类对象,作为这个数据访问的入口。数组类本身不是通过类加载器创建,而是由jvm直接创建
验证
验证的目的是验证字节流中包含的信息符合当前虚拟机的要求。
1、文件格式验证,是否以魔数0xCAFEBABE开头开始,版本信息是否为jvm接受,常量池中是否有不支持的类型。
2、元数据验证:进行语法分析,是否每个类都有父类,是否有语法错误,是否继承自final。
3、字节码验证:语义分析,通过数据流和控制流分析,看语义是否合法。
4、符号引用验证:就是看看是否能找到对应的类。发生在将符号引用转化为直接引用时,在解析中产生。
准备
正式为类变量分配内存,在方法区中分配
注意:static+final修饰的变量在准备阶段之后就是用户指定的值。
解析
将符号引用转化为直接引用,包括类,接口, 字段,方法的解析。
初始化
执行Java程序中的代码字节码,是执行类构造器的过程,对类的静态变量和代码块执行初始化工作。
1、clinit方法是由于编译器自动收集类中所有类变量赋值,静态语句块合并产生的,顺序是语句在源文件中的顺序。
2、clinit方法与类的构造器不同,他不需要显示的调用父类的构造方法,因为jvm会保证在子类的clinit方法执行之前,父类已经执行了。所以jvm执行的clinit一定是object类。
3、如果一个类或者接口中没有静态语句或者静态块,则可以没有clinit方法。
4、jvm会保证类的clinit方法加锁。 
注意:静态语句块中只能访问定义在静态语句之前的变量,不能访问语句之后的,但可以为后边的变量赋值。 

三、类加载器

类加载工作是有ClassLoader及其子类负责的,ClassLoader是一个运行时组件,它负责在运行时查找和装入Class字节码文件。jvm有三个ClassLoader:依次是:根装载器、扩展类加载器、应用程序类加载器。
1)启动类加载器(Bootstrap ClassLoader):负责加载JAVA_HOME\lib目录中或者被-Xbootclasspath参数限定的类,并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。
2)扩展类加载器(Extension ClassLoader):加载\lib\ext,或者被java.ext.dirs系统变量指定的类
3)应用程序类加载器(Application ClassLoader):该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
4)自定义类加载器,通过继承ClassLoader实现,一般是加载我们的自定义类 
根加载器
public class TestLoad  {

    public static void main(String[] args) {
        ClassLoader mClassLoader1=  Object.class.getClassLoader();
        //根类加载器打印出来为null,并不是没有加载器。
        System.out.println("Object对象的加载器为"+mClassLoader1);
    }
}
扩展类加载器
public class TestLoad {

    public static void main(String[] args) {
        ClassLoader mClassLoader1=  DNSNameService.class.getClassLoader();
        //扩展加载器打印出来
        // DNSNameService对象的加载器为sun.misc.Launcher$ExtClassLoader@330bedb4
        System.out.println("DNSNameService对象的加载器为"+mClassLoader1);
    }
}
应用程序类加载器
public class TestLoad {

    public static void main(String[] args) {
        ClassLoader mClassLoader1=  TestLoad.class.getClassLoader();
        //应用程序类加载器打印出来的值,我们之间用自己写的TestLoad来打印。
        // TestLoad对象的加载器为sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println("TestLoad对象的加载器为"+mClassLoader1);
    }
}

双亲委托机制:出于安全的考虑指加载的时候是一定是下通过其父类去找目标类,找不到才去找子类。此时的父类和子类并不是继承关系,只是包含关系。

image

ClassLoader 是Java.lang下的一个抽象方法,
1.Class loadClass(String name);name必须是参数的全限定名,
2.Class defineClass(String name,bytr[],int off,int len);
3.Class findClass(String name),查找具有指定的二进制名称的类
loadClass

双亲委托机制实现就是在loadClass

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先检测是否已经加载过该类
            Class<?> c = findLoadedClass(name);
            //先调用父加载器。进行类的加载,加载不成功,才用自己的类加载器
            if (c == null) {
                try {
                     //如果父类加载器不为空,则用父类加载器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //为空则用根加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                //还是加载不出来,用自己的类加载器
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
findClass
 查找具有指定的二进制名称的类
 protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
defineClass
 不能被覆盖,主要把byte[]转为虚拟机能识别的class对象。
    @Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        throw new UnsupportedOperationException("can't load this type of class file");
    }
   
URLClassLoader
  1. 首先编写一个Hello
package com.km.demo;

public class Hello {
    public Hello(){
        System.out.println("newInstance");
    }
}
  1. 编译这个Hello类,得到class文件。
  2. 书写读取代码,加载类,并且实例化这个类
public class TestLoad {
    public static void main(String[] args) throws Exception{
        # /Users/mac/Desktop/com/km/demo/Hello.class
        File mFile=new File("/Users/mac/Desktop/");
        URL mURL=mFile.toURI().toURL();
        //通过文件路径加载类
        URLClassLoader mURLClassLoader=new URLClassLoader(new URL[]{mURL});
        System.out.println("父类加载器:"+mURLClassLoader.getParent());
        Class mClass= mURLClassLoader.loadClass("com.km.demo.Hello");
        //实例化Hello类
        mClass.newInstance();
    }
}

输出

父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
newInstance
自定义类加载器

我们一般自定义类加载器的时候,都是复写findClass,然后调用defineClass。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class KmClassLoader extends ClassLoader {
    private String directory;
    private ClassLoader parent;

    public KmClassLoader(String directory) {
        this.directory = directory;
    }

    public KmClassLoader(ClassLoader parent, String directory) {
        super(parent);
        this.directory = directory;
        this.parent = parent;
    }

    @Override
    protected Class<?> findClass(String name){
        try {
            //重写findClass
            //文件地址
            String file=directory+ File.separator+name.replace(".",File.separator)+".class";
            //构建输入流
            InputStream inputStream=new FileInputStream(file);
            //构建输出流
            ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
            int lenth=-1;
            byte[] buffer=new byte[1024];
            while ((lenth=inputStream.read(buffer))!=-1){
                outputStream.write(buffer,0,lenth);
            }
            //得到字节数组
            byte[] reslut=outputStream.toByteArray();
            inputStream.close();
            outputStream.close();
            //调用defineClass得到Class
            return defineClass(name,reslut,0,reslut.length);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;

    }
    public static void main(String[] args) throws Exception{
        //通过文件路径加载类
        KmClassLoader mURLClassLoader=new KmClassLoader("/Users/mac/Desktop/");
        System.out.println("父类加载器:"+mURLClassLoader.getParent());
        Class mClass= mURLClassLoader.loadClass("com.km.demo.Hello");
        //实例化Hello类
        mClass.newInstance();
    }

}

输出

父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
newInstance
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值