二、类加载

二、类加载,第1张

  • loading 加载
  • linking  连接
    • verification  校验文件符合jvm规范
    • preparetion 准备(静态成员变量赋默认值)
    • resolution  解析 (将类,方法,属性等符号引用解析为直接引用)
  • initializing  初始化(调用类的,给静态成员变量赋值)---线程安全(可以实现单列模式)
classloader(类加载器)
  1. 启动类加载器
    1. 使用c/c++实现嵌套在jvm内部
    2. 加载java核心库
    3. 只加载包名为java,javax,sun开头的类
  2. 扩展类加载器
    1. java语言,sun.misc.Launcher.$ExtClassLoader实现
    2. 父类加载器为启动类加载器
    3. 加载java.ext.dirs/jre/lib/ext指定的目录
  3. 应用程序类加载器
    1. java语言,sun.misc.Launcher.$AppClassLoader实现
    2. 父类加载器为扩展类加载器
    3. 加载环境变量classpath或者系统属性java.class.path指定路径下的类
    4. 用户自定义类加载器的默认父加载器

获取当前class的classloader   xxx.class.getClassLoader()

双亲委派模型 

      从jdk1.2开始类的加载过程采用双亲委派机制,该机制规定了加载顺序是,引导类加载器先加载,如果加载不了,由扩展类加载器加载,若还加载不了,才会是系统类加载器或者在自定义类加载器进行加载。

  Java代码验证:ClassLoader.loadClass方法

protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    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)保护程序安全,防止核心API被随意篡改

劣势

顶层的ClassLoader无法访问底层的ClassLoader所加载的类,比如Tomcat中,类加载器采用先自己加载,如果加载失败,再委派给父类加载器加载,servlet规范推荐使用该做法。

类加载范围:参考Launcher里面定义加载范围,通过获取系统属性确定加载范围。

自定义类加载器 
  • 继承classloader
  • 重新模板方法findclass
    • 调用defineClass   
  • 自定义类加载器加载自己加密过的class
    • 防止反编译
public class TestClassLoader extends ClassLoader{
    
    private int seed=11055544;
    
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            FileInputStream fileInputStream = new FileInputStream(new File(name));
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int i=0;
            while ((i=fileInputStream.read())!=0){
                byteArrayOutputStream.write(i^seed);//加密
            }
            byte[] bytes = byteArrayOutputStream.toByteArray();
            byteArrayOutputStream.close();
            fileInputStream.close();
            return defineClass(name,bytes,0,bytes.length);
        }catch (Exception e){
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws ClassNotFoundException {
        TestClassLoader testClassLoader = new TestClassLoader();
        Class aClass = testClassLoader.loadClass("c://zczhang//app.java");
    }
}
打破双亲委派模型
  • jdk1.2之前,自定义的classloader必须重写loadclass()方法,所以可以打破;
  • 自定义类加载器,重写loadClass方法;
  • 使用线程上下文类加载器;
  • 热部署,热加载(因为源码里面默认缓存里面找,如果有就不加载了)
    • osgi tomact都自己的模块指定classloader

类加载触发时机(lazyLoading)
  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没 有进行过初始化,则需要先触发其初始化。
  • 使用java.lang.reflect包的方法对类进行发射调用的时候
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发 其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个 类),虚拟机会先初始化这个主类。
  • 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
  • 当使用 JDK1.7 的动态方法调用时,如果一个 MethodHandler 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄没有初始化,则需要先触发类初始化。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/793859.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存