>Java >Java베이스 >Java 클래스 로더에 대한 자세한 설명 ClassLoader

Java 클래스 로더에 대한 자세한 설명 ClassLoader

angryTom
angryTom앞으로
2019-11-27 15:35:502346검색

Java 클래스 로더에 대한 자세한 설명 ClassLoader

ClassLoader를 얻는 방법

1. ClassLoader

clazz.getClassLoader()

2. 현재 스레드 컨텍스트의 ClassLoader를 가져옵니다

Thread.currentThread().getContextClassLoader();

3. 시스템의 ClassLoader

ClassLoader.getSystemClassLoader()

4를 가져옵니다. # 🎜🎜#

DriverManager.getCallerClassLoader

ClassLoader 소스 코드 분석

(권장 학습: Java 비디오 튜토리얼) # 🎜🎜 #Overview

클래스 로더는 클래스를 로드하는 데 사용되는 객체이고 ClassLoader는 추상 클래스입니다. 클래스의
바이너리 이름

이 주어지면 클래스 로더는 정의 클래스를 구성하는 데이터를 찾거나 생성하려고 시도해야 합니다. 일반적인 전략은 주어진 바이너리 이름을 파일 이름으로 변환한 다음 파일 시스템에서 이 파일 이름에 해당하는 클래스 파일을 읽는 것입니다. 각 클래스 객체에는 이를 정의하는 ClassLoader에 대한 참조가 포함됩니다.

배열 클래스의 클래스 객체는 클래스 로더에 의해 생성되지 않지만 Java 런타임 중에 필요에 따라 JVM에 의해 자동으로 생성됩니다. 배열 클래스의 클래스 로더의 경우

Class.getClassLoader()

을 통해 반환되는데, 이는 배열의 요소 유형인 경우 배열의 요소 유형의 클래스 로더와 동일합니다. 네이티브 유형이고 배열 클래스에는 클래스 로더가 없습니다[코드 1]. 이 애플리케이션은 JVM이 동적으로 클래스를 로드하는 방식을 확장하기 위해 ClassLoader의 하위 클래스를 구현합니다.

클래스 로더는 일반적으로 보안 관리자가 보안 도메인 문제를 식별하는 데 사용됩니다.

ClassLoader 클래스는 클래스와 리소스를 찾기 위해 위임 모델을 사용합니다. ClassLoader의 각 인스턴스에는 이와 연결된 상위 ClassLoader가 있습니다. ClassLoader가 클래스나 리소스를 찾으라는 요청을 받으면 ClassLoader 인스턴스는 Before입니다. 클래스나 리소스를 찾으려고 하면 이를 상위 클래스 로더에 위임합니다. 스타트업 클래스 로더라고 불리는 가상 머신의 내장 클래스 로더는 부모 로더가 없지만 클래스 로더의 부모 클래스 로더로 사용할 수 있습니다. [

부모 위임 메커니즘

]. 동시 클래스 로딩을 지원하는 클래스 로더를 병렬 클래스 로더라고 합니다. 초기화 시

ClassLoader.registerAsParallelCapable

메소드를 통해 자신을 등록해야 합니다. 기본적으로 병렬로 등록되지만 해당 하위 클래스도 병렬로 로드되는 경우 하위 클래스를 별도로 등록해야 합니다. 위임 모델이 엄격하게 계층적이지 않은 환경에서는 클래스 로더가 병렬이어야 합니다. 그렇지 않으면 클래스 로딩 프로세스 중에 로더의 잠금이 항상 유지되므로 클래스 로딩으로 인해 교착 상태가 발생합니다.

일반적으로 Java 가상 머신은 플랫폼에 따라 다른 방식으로 로컬 파일 시스템에서 클래스를 로드합니다. 예를 들어 UNIX 시스템에서는 가상 머신이 CLASSPATH 환경에 정의된 디렉터리에서 클래스를 로드합니다.

그러나 일부 클래스는 파일에서 가져오지 않으며 네트워크와 같은 다른 소스에서 가져오거나 [동적 프록시]가 애플리케이션 자체에 의해 구축됩니다. DefineClass 메소드는 바이트 배열을 Class의 인스턴스로 변환합니다. 새로 정의된 클래스의 인스턴스는

Class.newInstance
에 의해 생성될 수 있습니다. 클래스 로더에 의해 생성된 객체의 메서드와 생성자는 다른 클래스를 참조할 수 있습니다. 참조된 클래스를 확인하기 위해 Java 가상 머신은 원래 생성된 클래스 로더의

loadClass를 호출합니다.

방법. 바이너리 이름: 문자열 매개변수 형식으로 CalssLoader에 제공되는 모든 클래스 이름은 다음 네 가지 상황을 포함하여 바이너리 이름이어야 합니다.

"java. lang.String" 일반 클래스
  • "javax.swing.JSpinner$DefaultEditor" 내부 클래스
  • "java.security.KeyStore
  • (빌더)# 🎜🎜# FileBuilder$1" KeyStore의 내부 클래스 Builder의 내부 클래스 FileBuilder의 첫 번째 익명 내부 클래스
  • "java.net.URLClassLoader$3$1" URLClassLoader 클래스의 세 번째 익명 내부 클래스 익명 내부 클래스
  • # 🎜🎜#
  • Code 1:
  • public class Test12 {
        public static void main(String[] args) {
            String[] strings = new String[6];
            System.out.println(strings.getClass().getClassLoader());
            // 运行结果:null
    
            Test12[] test12s = new Test12[1];
            System.out.println(test12s.getClass().getClassLoader());
            // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2
    
            int[] ints = new int[2];
            System.out.println(ints.getClass().getClassLoader());
            // 运行结果:null
        }
    }
loadClass method

#🎜🎜 #loadClass의 소스 코드는 다음과 같습니다. 지정된 바이너리 이름을 가진 클래스 기본적으로 다음 순서로 클래스를 검색합니다.

a) findLoadedClass(String)를 호출하여 이 클래스가 로드되었는지 확인합니다# 🎜🎜# b) 상위 클래스 로더의 loadClass 메소드를 호출합니다. 상위 클래스 로더가 null이면 시작 클래스 로더는

c) findClass(String) 메소드를 호출하여

을 찾습니다.

위 단계를 사용하여 클래스가 발견되고 해결이 true이면 해결클래스(클래스) 메서드가 호출됩니다.

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;
  }
}

findClass 메서드

#🎜🎜 #

findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定义类加载器

/**
 * 继承了ClassLoader,这是一个自定义的类加载器
 * @author 夜的那种黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 类加载器名称,标识作用
     */
    private String classLoaderName;

    /**
     * 从磁盘读物字节码文件的扩展名
     */
    private String fileExtension = ".class";

    /**
     * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(String classLoaderName) {
        // 将系统类加载器当做该类加载器的父加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 创建一个类加载器对象,显示指定该类加载器的父加载器
     * 前提是需要有一个类加载器作为父加载器
     * @param parent 父加载器
     * @param classLoaderName 类加载器名称
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 显示指定该类加载器的父加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制
     * 该方法会在检查完父类加载器之后被loadClass方法调用
     * 默认返回ClassNotFoundException异常
     * @param className 类名
     * @return Class的实例
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通过defineClass方法将字节数组转换为Class
         * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定义类加载器的代码,我们执行这段代码

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。

改造以下代码,添加一个path属性用来指定类加载位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上为测试代码---------------------------------

    /**
     * 从指定路径加载
     */
    private String path;

    ......
    
    /**
     * io操作,根据类名找到对应文件,返回class文件的二进制信息
     * @param className 类名
     * @return class文件的二进制信息
     * @throws ClassNotFoundException 如果类不能被找到,抛出此异常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

运行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下测试代码,并删除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

运行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。

再来测试一块代码

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。

我们可以通过设置父类加载器来让loader和loader2处于同一命名空间

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

运行结果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

扩展:命名空间

1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成

2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类

3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

php中文网,大量的免费Java入门教程,欢迎在线学习!  

위 내용은 Java 클래스 로더에 대한 자세한 설명 ClassLoader의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제