深入分析ClassLoader工作机制

ClassLoader 较为深入分析。

from <深入分析Java Web>



加载CLASS到JVM中,审查每个类应该由谁加载,父优先的等级加载机制。


加载机制

ClassLoader类结构分析

ClassLoader抽象类,有很多子类,一般在实现自己的ClassLoader时候,一般都会继承URLClassLoader这个子类,因为这个类已经实现了大部分的工作,就像Servlet通过会直接HttpServlet一样。

打开源码:

几个重要的方法

  • protected final Class

等级加载机制



一层一层判断是否应该由本层ClassLoader加载,并通过在哪一层加载确定类的加载级别。

Bootstrap ClassLoader :主要加载JVM自身工作需要的类,这个ClassLoader完全由JVM自己控制的。不准守普通的加载规则,没有父类和子类。

sun.misc.Launcher java程序的入口就是sun.misc.Launcher,jdk的扩展类加载器ExtClassLoader和系统类加载器AppClassLoader都是Launcher的内部类。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。下列代码是Launcher构建的过程。

public Launcher() {

        Launcher.ExtClassLoader var1;

        ...

            var1 = Launcher.ExtClassLoader.getExtClassLoader();

        ...

            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

....

}

类图:

  可以看到APPClassLoader和ExtClassLoader都继承自URLClassLoader,在构建Launcher过程中,先创建ExtClassLoader,然后根据ExtClassLoader作为父加载器创建APPClassLoader。然后使用Launcher的getLauncher方法得到的就是APPClassLoader。如果在Java应用中没有其他的ClassLoader,则除了”java.ext.dirs”目录下的类由ExtClassLoader加载的外,其他的都是由APPClassLoader。看一下源码。

ExtClassLoader:

    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {

            final File[] var0 = getExtDirs();//这里

            try {

                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction() {

                    public Launcher.ExtClassLoader run() throws IOException {

                        int var1 = var0.length;

                        for(int var2 = 0; var2 < var1; ++var2) {

                            MetaIndex.registerDirectory(var0[var2]);

                        }

                        return new Launcher.ExtClassLoader(var0);///这里

                    }

                });

            } catch (PrivilegedActionException var2) {

                throw (IOException)var2.getException();

            }

        }

     private static File[] getExtDirs() {

                String var0 = System.getProperty("java.ext.dirs");

    ...

    }

APPClassLoader:使用ExtClassLoader作为父类加载器

 static class AppClassLoader extends URLClassLoader {

        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {

            final String var1 = System.getProperty("java.class.path");//这里

            final File[] var2 = var1 == null?new File[0]:Launcher.getClassPath(var1);

            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {

                public Launcher.AppClassLoader run() {

                    URL[] var1x = var1 == null?new URL[0]:Launcher.pathToURLs(var2);

                    return new Launcher.AppClassLoader(var1x, var0);//这里

                }

            });

        }

这是URLClassLoader的:

 public URLClassLoader(URL[] urls, ClassLoader parent,....){}

加载Class文件到内存中的2种方式

一种是隐式加载,一种是显式加载。

  1. 隐式加载: 就是不需要在代码中调用ClassLoader来加载的类,而是通过JVM来自动加载这些需要的类到内存中的方式,比如在我们继承或引用某些类的时候,JVM分析当前类引用的类不在内存中,那么就会自动加载类到内存中。
  2. 显示加载:就是在代码中调用ClassLoader来加载一个类,比如this.getClass().getClassLoader().loadClass()或者Class.forName() 或者自定义的ClassLoader的loadClass方法。

混合的方式,就是在自定义ClassLoader时候引用其他类,就算是隐式加载。


加载class的过程



加载过程,简单地看了一下代码。大概是:

—>findClass(final String name)

—> defineClass(String name, Resource res)

—>Class

加载字节码到内存中



在抽象类ClassLoader中,很多实现都交给子类实现,如如何找到,如何加载到内存中。就是findClass()方法。最常用的URLClassLoader的findClass源码如下;

 protected Class<?> findClass(final String name)

             throws ClassNotFoundException

        {

            try {

                return AccessController.doPrivileged(

                    new PrivilegedExceptionAction<Class>() {

                        public Class run() throws ClassNotFoundException {

                            String path = name.replace(‘.‘, ‘/‘).concat(".class");

                            Resource res = ucp.getResource(path, false);

                            if (res != null) {

                                try {

                                    return defineClass(name, res);

                                } catch (IOException e) {

                                    throw new ClassNotFoundException(name, e);

                                }

                            } else {

                                throw new ClassNotFoundException(name);

                            }

                        }

                    }, acc);

            } catch (java.security.PrivilegedActionException pae) {

                throw (ClassNotFoundException) pae.getException();

            }

        }

简要说明一下:首先将Path,类名的包定义,由’.’换为路径的’/’。并将.class加上。然后ucp(URLClassPath类型)定义哪里找到这个class文件,读取它的字节流。然后通过defineClass来实例类class对象。根据URL传过来的是Jar包还是实际Class文件,就能创建不同的Loader了,这样就实现加载Class文件到内存中。下图还可以看出Loader是URLClassPath的内部类。


常见加载类错误分析


ClassNotFoundException

这个非常常见的错误,一般问题是找不到.class文件,就是classpath路径和你想要的class文件路径不在一个地方。如果不知道classpath路径是什么,可以通过下列代码方式找到。

    this.getClass().getClassLoader().getResource("").toString();

通常加载一个不知道的类时就会发生这样的错误。

 public class notFound{

        public static void main(String []args){

            try {

                Class.forName("NotFoundClass");

            }catch(ClassNotFoundException e){ 

                e.printStackTrace();

            }

        }

    }

显示加载的几种方式:

  • Class.forName();
  • ClassLoader的loadClass();
  • ClassLoader的findSystemClass();


UnsatisfiedLinkError

这个问题不常见,出现的场景就是调用某些本地lib中的native方法时,没有找到这样的lib。报出的错误。

/**

     * 文件描述:

     * 作者: bamboo

     * 时间: 2016/12/9

     */

    public class NoLibException {

        public native void nativeMethod();

        static {

            System.loadLibrary("NoLib");

        }

        public static void main(String[] args) {

            new NoLibException().nativeMethod();

        }

    }

    java.lang.UnsatisfiedLinkError: no NoLib in java.library.path

    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886)

    at java.lang.Runtime.loadLibrary0(Runtime.java:849)

    at java.lang.System.loadLibrary(System.java:1088)

    at CCF.NoLibException.<clinit>(NoLibException.java:11)

ClassCastException



强制转换异常,比如说想把String强制转换为Integer

代码

import java.util.HashMap;

    import java.util.Map;

    /**

     * 文件描述:

     * 作者: bamboo

     * 时间: 2016/12/9

     */

    public class CastException {

        public static Map m = new HashMap() {

            {

                put("a", "2");

            }

        };

        public static void main(String[] args) {

            Integer integer= (Integer) m.get("a");

            System.out.println(integer);

        }

    }

结果:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

        at CCF.CastException.main(CastException.java:19)

        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

        at java.lang.reflect.Method.invoke(Method.java:606)

        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

实现自己的ClassLoader

之前写的博客。

http://blog.csdn.net/newpidian/article/details/52831121 主要实现以下3个功能

  • 获取类加载器
  • 根据类名称加载类
  • 获取指定包下的所有类

加载自定义格式的Class文件

实现一个从远端服务器,经过加密的class文件的网络传输,在本地解密,并通过defineClass方法创建这个类的实例的方法。

代码如下:

import java.io.ByteArrayOutputStream;

    import java.io.File;

    import java.io.InputStream;

    import java.net.URL;

    /**

     * 文件描述:

     * 作者: bamboo

     * 时间: 2016/12/9

     */

    public class NetClassLoader extends ClassLoader {

        private String classPath;

        private String packageName;

        public NetClassLoader(ClassLoader parent, String classPath, String packageName) {

            super(parent);

            this.classPath = classPath;

            this.packageName = packageName;

        }

        public NetClassLoader(String classPath, String packageName) {

            this.classPath = classPath;

            this.packageName = packageName;

        }

        @Override

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

            Class<?> aClass = findLoadedClass(name);

            if (aClass != null) {

                return aClass;

            }

            if (packageName.startsWith(name)) {

                byte[] classData = getData(name);

                if (classData == null) {

                    throw new ClassNotFoundException();

                } else {

                    defineClass(name, classData, 0, classData.length);

                }

            }

            return super.loadClass(name);

        }

        private byte[] getData(String className) {

            String path = classPath + File.separator + className.replace(‘.‘, File.separatorChar) + ".class";

            try {

                URL url = new URL(path);

                InputStream is = url.openStream();

                ByteArrayOutputStream stream = new ByteArrayOutputStream();

                byte[] buffer = new byte[2048];

                int num = 0;

                while ((num = is.read(buffer)) != -1) {

                    stream.write(buffer, 0, num);

                }

                return stream.toByteArray();

            } catch (Exception e) {

                e.printStackTrace();

            }

            return null;

        }

        private byte[] deCode(byte[] src) {

            byte[] decode = null;

            ///字节编码处理

            return decode;

        }

    }

##实现类的热部署

在JVM加载类之前会先判定要请求的类是否已经被加载过来,就是通过findLoadedClass()来返回类实例,如果已经加载了,则loadClass()会冲突,如何判断是不是同一个类有2个条件。

- 类名是否完全一样,包括包名。

- 看这个类的ClassLoader是否完全一样。这里指的是是不是同一个ClassLoader实例。即使是同一个ClassLoader类的不同实例,加载的类也不一样。

所以实现热部署,可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名类。

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.InputStream;

/**

 * 文件描述:

 * 作者: bamboo

 * 时间: 2016/12/9

 */

public class ClassReloader extends ClassLoader {

    private String classPath;

    public ClassReloader(ClassLoader parent, String classPath) {

        super(parent);

        this.classPath = classPath;

    }

    public ClassReloader(String classPath) {

        this.classPath = classPath;

    }

    @Override

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

        byte[] classData = getData(name);

        if (classData == null) {

            throw new ClassNotFoundException();

        } else {

            return defineClass(name, classData, 0, classData.length);

        }

    }

    private byte[] getData(String className) {

        String path = classPath +  className+".class";

        try {

            InputStream is = new FileInputStream(path);

            ByteArrayOutputStream stream = new ByteArrayOutputStream();

            byte[] buffer = new byte[2048];

            int num = 0;

            while ((num = is.read(buffer)) != -1) {

                stream.write(buffer, 0, num);

            }

            return stream.toByteArray();

        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;

    }

    public static void main(String[] args) {

        try {

            String path = "C:\\Users\\bamboo\\Desktop\\单源\\Leetcode\\out\\";

            ClassReloader reloader = new ClassReloader(path);

            Class r = reloader.findClass("AllOne");

            System.out.println((r.newInstance()));

            ClassReloader reloader1 = new ClassReloader(path);

            Class r1 = reloader1.findClass("AllOne");

            System.out.println((r1.newInstance()));

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}


输出

    AllOne@26a30589

    [email protected]

如果将 `   Class r1 = reloader1.findClass("AllOne");`改为

                    Class r1 = reloader.findClass("AllOne");

则会抛出duplicate class definition,类的重复定义。

[email protected]

Exception in thread "main" java.lang.LinkageError: loader (instance of  ParallelBasic/ClassReloader): attempted  duplicate class definition for name: "AllOne"

    at java.lang.ClassLoader.defineClass1(Native Method)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:643)

    at ParallelBasic.ClassReloader.findClass(ClassReloader.java:32)

    at ParallelBasic.ClassReloader.main(ClassReloader.java:61)

    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

    at java.lang.reflect.Method.invoke(Method.java:606)

    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

“`

一个问题:如果使用不同的ClassLoader实例加载同一个类会怎么样,会不会导致JVM的PermGen(永久代)n区无限增大??答案是否定的,why,因为ClassLoader对象也是对象,在没有被持有引用的时候,也会被JVM回收。值得注意的是,被ClassLoader加载的类的字节码会一直保存在JVM的PermGen中,这个数据一般是在Full GC的时候才会被回收,所以,如果应用大量使用动态类加载。Full GC又不是太频繁,也要注意PermGen的大小,防止内存溢出。


时间: 2024-10-14 00:00:40

深入分析ClassLoader工作机制的相关文章

详细深入分析 ClassLoader 工作机制

申明:本文首发于 详细深入分析 Java ClassLoader 工作机制 ,如有转载,注明原出处即可,谢谢配合. 详细深入分析 Java ClassLoader 工作机制 什么是 ClassLoader ClassLoader 作用 1ClassLoader 类结构分析 2ClassLoader 的等级加载机制 Java默认提供的三个ClassLoader ClassLoader加载类的原理 原理介绍 2为什么要使用双亲委托这种模型呢 3 但是JVM在搜索类的时候又是如何判定两个class是相

第六章 深入分析ClassLoader工作机制

classLoader是类加载器,负责将Class加载到JVM中,还有一个作用是审查每个类由谁加载,它是一种父优先的等级加载机制. 还有一个任务是,将class字节码重新解析成JVM统一要求的对象格式. 6.1 ClassLoader类结构分析 6.2 ClassLoader的等级加载机制 6.3 如何加载class文件 6.3.1 加载字节码到内存 6.3.2 验证与解析 6.3.3 初始化class对象 6.4 常见加载类错误分析   6.4.1 ClassNotFoundException

深入理解ClassLoader工作机制(jdk1.8)

ClassLoader 顾名思义就是类加载器,ClassLoader 作用: 负责将 Class 加载到 JVM 中    审查每个类由谁加载(父优先的等级加载机制)    将 Class 字节码重新解析成 JVM 统一要求的对象格式 类加载时机与过程 类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载.验证.准备.解析.初始化.使用和卸载这7个阶段.其中,验证.准备和解析这三个部分统称为连接(linking).这里写图片描述 其中,加载.验证.准备.初始化和卸载这五

ClassLoader工作机制

本人在研究生阶段做JavaWeb开发时,当把项目发布到Tomcat上运行时,曾遇到过一个神奇的问题,这个问题由"CLassCastException"异常引起,当然这个异常很常见,其实我要说的神奇之处在于------我明明使用的是这个类本身new的对象,然后编译没有问题,但运行时却抛出这个异常.类似这样:MyClass mc=new MyClass(),异常就发生在这一句,是不是很奇怪了.为什么会类型转换失败? 一般来说,产生ClassCastException的主要原因有以下两点: 

ClassLoader 工作机制

ClassLoader 采用上级委托接待机制加载 class JVM 平台提供三层 ClassLoader 1.Bootstrap ClassLoader:主要加载 JVM 自身工作需要的类 2.ExtClassLoader:加载 VIP 类 3.AppClassLoader:加载自定义类

深入分析 Java I/O 的工作机制

I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动.这个流动的过程中都涉及到 I/O 问题,可以说大部分 Web 应用系统的瓶颈都是 I/O 瓶颈.本文的目的正是分析 I/O 的内在工作机制,你将了解到:Java 的 I/O 类库的基本架构:磁盘 I/O 工作机制:网络 I/O 的工作机制:其中以网络 I/O 为重点介绍 Java Socket 的工作方式:你还将了解到 NIO 的工作方式,还有同步和异步以及阻塞与非阻塞的区别,最

深入分析 Java I/O 的工作机制(转载)

声明:本文转自 http://www.ibm.com/developerworks/cn/java/j-lo-javaio/ I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动.这个流动的过程中都涉及到 I/O 问题,可以说大部分 Web 应用系统的瓶颈都是 I/O 瓶颈.本文的目的正是分析 I/O 的内在工作机制,你将了解到:Java 的 I/O 类库的基本架构:磁盘 I/O 工作机制:网络 I/O 的工作机制:其中以网络 I/

java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制

ClassLoader的工作机制 java应用环境中不同的class分别由不同的ClassLoader负责加载. 一个jvm中默认的classloader有Bootstrap ClassLoader.Extension ClassLoader.App ClassLoader,分别各司其职: Bootstrap ClassLoader     负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar.resources.jar.charsets.jar和class等 Ex

深入分析 Java I/O 的工作机制--转载

Java 的 I/O 类库的基本架构 I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道.在当今这个数据大爆炸时代,I/O 问题尤其突出,很容易成为一个性能瓶颈.正因如此,所以 Java 在 I/O 上也一直在做持续的优化,如从 1.4 开始引入了 NIO,提升了 I/O 的性能.关于 NIO 我们将在后面详细介绍. Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以