자바의 클래스 로더

1. 클래스 로더 소개

클래스 로더는 런타임 중에 Java 클래스를 JVM (Java Virtual Machine)에 동적으로로드 하는 역할을 합니다. 또한 JRE (Java Runtime Environment)의 일부입니다. 따라서 JVM은 클래스 로더 덕분에 Java 프로그램을 실행하기 위해 기본 파일이나 파일 시스템에 대해 알 필요가 없습니다.

또한 이러한 Java 클래스는 한 번에 메모리에로드되지 않고 응용 프로그램에서 필요할 때로드됩니다. 이것은 클래스 로더가 등장하는 곳입니다. 클래스를 메모리에로드하는 역할을합니다.

이 튜토리얼에서는 다양한 유형의 내장 클래스 로더, 작동 방식 및 자체 사용자 정의 구현 소개에 대해 설명합니다.

2. 내장 클래스 로더 유형

간단한 예제를 사용하여 다양한 클래스 로더를 사용하여 다양한 클래스가로드되는 방법을 배우는 것으로 시작하겠습니다.

public void printClassLoaders() throws ClassNotFoundException { System.out.println("Classloader of this class:" + PrintClassLoader.class.getClassLoader()); System.out.println("Classloader of Logging:" + Logging.class.getClassLoader()); System.out.println("Classloader of ArrayList:" + ArrayList.class.getClassLoader()); }

위의 메서드를 실행하면 다음과 같이 인쇄됩니다.

Class loader of this class:[email protected] Class loader of Logging:[email protected] Class loader of ArrayList:null

보시다시피 여기에는 세 가지 클래스 로더가 있습니다. 응용 프로그램, 확장 및 부트 스트랩 ( null로 표시됨).

애플리케이션 클래스 로더는 예제 메소드가 포함 된 클래스를로드합니다. 애플리케이션 또는 시스템 클래스 로더는 클래스 경로에 자체 파일을로드합니다.

다음으로 확장 1은 Logging 클래스를 로드합니다 . 확장 클래스 로더는 표준 코어 Java 클래스의 확장 인 클래스를로드합니다.

마지막으로 부트 스트랩은 ArrayList 클래스를 로드합니다 . 부트 스트랩 또는 원시 클래스 로더는 다른 모든 로더의 상위입니다.

그러나 마지막 출력은 ArrayList의 경우 출력에 null 을 표시 한다는 것을 알 수 있습니다 . 이는 부트 스트랩 클래스 로더가 Java가 아닌 네이티브 코드로 작성 되었기 때문에 Java 클래스로 표시되지 않기 때문입니다. 이러한 이유로 부트 스트랩 클래스 로더의 동작은 JVM마다 다릅니다.

이제 이러한 각 클래스 로더에 대해 자세히 설명하겠습니다.

2.1. 부트 스트랩 클래스 로더

Java 클래스는 java.lang.ClassLoader 의 인스턴스에 의해로드됩니다 . 그러나 클래스 로더는 클래스 자체입니다. 따라서 질문은 누가 java.lang.ClassLoader 자체 를로드 합니까?

여기에서 부트 스트랩 또는 원시 클래스 로더가 등장합니다.

주로 JDK 내부 클래스 (일반적으로 rt.jar$ JAVA_HOME / jre / lib 디렉토리 에있는 기타 핵심 라이브러리) 를로드하는 역할을 합니다. 또한 부트 스트랩 클래스 로더는 다른 모든 ClassLoader 인스턴스 의 부모 역할을 합니다 .

이 부트 스트랩 클래스 로더는 핵심 JVM의 일부이며 위의 예에서 지적한대로 원시 코드작성됩니다 . 플랫폼마다이 특정 클래스 로더의 구현이 다를 수 있습니다.

2.2. 확장 클래스 로더

확장 클래스 로더는 부트 스트랩 클래스 로더의 자식 표준 핵심 자바 클래스의 확장을 로딩을 담당 가 플랫폼에서 실행되는 모든 응용 프로그램에 사용할 수 있도록.

확장 클래스 로더는 JDK 확장 디렉토리 (일반적으로 $ JAVA_HOME / lib / ext 디렉토리 또는 java.ext.dirs 시스템 특성에 언급 된 기타 디렉토리)에서 로드 됩니다.

2.3. 시스템 클래스 로더

반면에 시스템 또는 애플리케이션 클래스 로더는 모든 애플리케이션 레벨 클래스를 JVM으로로드합니다. classpath 환경 변수, -classpath 또는 -cp 명령 줄 옵션 에있는 파일을로드합니다 . 또한 확장 클래스 로더의 하위입니다.

3. 클래스 로더는 어떻게 작동합니까?

클래스 로더는 Java Runtime Environment의 일부입니다. JVM이 클래스를 요청할 때 클래스 로더는 완전한 클래스 이름을 사용하여 클래스를 찾고 런타임에 클래스 정의를로드하려고합니다.

java.lang.ClassLoader.loadClass () 메서드는 런타임에 클래스 정의를로드 할 책임이있다 . 정규화 된 이름을 기반으로 클래스를로드하려고합니다.

클래스가 아직로드되지 않은 경우 요청을 상위 클래스 로더에 위임합니다. 이 프로세스는 재귀 적으로 발생합니다.

결국 부모 클래스 로더가 클래스를 찾지 못하면 자식 클래스는 java.net.URLClassLoader.findClass () 메서드를 호출 하여 파일 시스템 자체에서 클래스를 찾습니다.

마지막 하위 클래스 로더가 클래스를로드 할 수없는 경우 java.lang.NoClassDefFoundError 또는 java.lang.ClassNotFoundException이 발생합니다.

ClassNotFoundException이 발생했을 때 출력 예를 살펴 보겠습니다.

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348)

java.lang.Class.forName () 호출에서 바로 일련의 이벤트 를 살펴보면 먼저 부모 클래스 로더를 통해 클래스를로드 한 다음 java.net.URLClassLoader.findClass () 를 통해 찾는다는 것을 이해할 수 있습니다. 클래스 자체.

여전히 클래스를 찾지 못하면 ClassNotFoundException이 발생합니다.

클래스 로더에는 세 가지 중요한 기능이 있습니다.

3.1. 위임 모델

클래스 로더는 위임 모델을 따르며, 클래스 또는 리소스를 찾기위한 요청시 ClassLoader 인스턴스가 클래스 또는 리소스 검색을 상위 클래스 로더에 위임합니다 .

Let's say we have a request to load an application class into the JVM. The system class loader first delegates the loading of that class to its parent extension class loader which in turn delegates it to the bootstrap class loader.

Only if the bootstrap and then the extension class loader is unsuccessful in loading the class, the system class loader tries to load the class itself.

3.2. Unique Classes

As a consequence of the delegation model, it's easy to ensure unique classes as we always try to delegate upwards.

If the parent class loader isn't able to find the class, only then the current instance would attempt to do so itself.

3.3. Visibility

In addition, children class loaders are visible to classes loaded by its parent class loaders.

For instance, classes loaded by the system class loader have visibility into classes loaded by the extension and Bootstrap class loaders but not vice-versa.

To illustrate this, if Class A is loaded by an application class loader and class B is loaded by the extensions class loader, then both A and B classes are visible as far as other classes loaded by Application class loader are concerned.

Class B, nonetheless, is the only class visible as far as other classes loaded by the extension class loader are concerned.

4. Custom ClassLoader

The built-in class loader would suffice in most of the cases where the files are already in the file system.

However, in scenarios where we need to load classes out of the local hard drive or a network, we may need to make use of custom class loaders.

In this section, we'll cover some other uses cases for custom class loaders and we'll demonstrate how to create one.

4.1. Custom Class Loaders Use-Cases

Custom class loaders are helpful for more than just loading the class during runtime, a few use cases might include:

  1. Helping in modifying the existing bytecode, e.g. weaving agents
  2. Creating classes dynamically suited to the user's needs. e.g in JDBC, switching between different driver implementations is done through dynamic class loading.
  3. Implementing a class versioning mechanism while loading different bytecodes for classes with same names and packages. This can be done either through URL class loader (load jars via URLs) or custom class loaders.

There are more concrete examples where custom class loaders might come in handy.

Browsers, for instance, use a custom class loader to load executable content from a website. A browser can load applets from different web pages using separate class loaders. The applet viewer which is used to run applets contains a ClassLoader that accesses a website on a remote server instead of looking in the local file system.

And then loads the raw bytecode files via HTTP, and turns them into classes inside the JVM. Even if these applets have the same name, they are considered as different components if loaded by different class loaders.

Now that we understand why custom class loaders are relevant, let's implement a subclass of ClassLoader to extend and summarise the functionality of how the JVM loads classes.

4.2. Creating Our Custom Class Loader

For illustration purposes, let's say we need to load classes from a file using a custom class loader.

We need to extend the ClassLoader class and override the findClass() method:

public class CustomClassLoader extends ClassLoader { @Override public Class findClass(String name) throws ClassNotFoundException { byte[] b = loadClassFromFile(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassFromFile(String fileName) { InputStream inputStream = getClass().getClassLoader().getResourceAsStream( fileName.replace('.', File.separatorChar) + ".class"); byte[] buffer; ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); int nextValue = 0; try { while ( (nextValue = inputStream.read()) != -1 ) { byteStream.write(nextValue); } } catch (IOException e) { e.printStackTrace(); } buffer = byteStream.toByteArray(); return buffer; } }

In the above example, we defined a custom class loader that extends the default class loader and loads a byte array from the specified file.

5. Understanding java.lang.ClassLoader

Let's discuss a few essential methods from the java.lang.ClassLoader class to get a clearer picture of how it works.

5.1. The loadClass() Method

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

This method is responsible for loading the class given a name parameter. The name parameter refers to the fully qualified class name.

The Java Virtual Machine invokes loadClass() method to resolve class references setting resolve to true. However, it isn't always necessary to resolve a class. If we only need to determine if the class exists or not, then resolve parameter is set to false.

This method serves as an entry point for the class loader.

We can try to understand the internal working of the loadClass() method from the source code of java.lang.ClassLoader:

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. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }

The default implementation of the method searches for classes in the following order:

  1. Invokes the findLoadedClass(String) method to see if the class is already loaded.
  2. Invokes the loadClass(String) method on the parent class loader.
  3. Invoke the findClass(String) method to find the class.

5.2. The defineClass() Method

protected final Class defineClass( String name, byte[] b, int off, int len) throws ClassFormatError

This method is responsible for the conversion of an array of bytes into an instance of a class. And before we use the class, we need to resolve it.

In case data didn't contain a valid class, it throws a ClassFormatError.

Also, we can't override this method since it's marked as final.

5.3. The findClass() Method

protected Class findClass( String name) throws ClassNotFoundException

This method finds the class with the fully qualified name as a parameter. We need to override this method in custom class loader implementations that follow the delegation model for loading classes.

Also, loadClass() invokes this method if the parent class loader couldn't find the requested class.

The default implementation throws a ClassNotFoundException if no parent of the class loader finds the class.

5.4. The getParent() Method

public final ClassLoader getParent()

This method returns the parent class loader for delegation.

Some implementations like the one seen before in Section 2. use null to represent the bootstrap class loader.

5.5. The getResource() Method

public URL getResource(String name)

This method tries to find a resource with the given name.

It will first delegate to the parent class loader for the resource. If the parent is null, the path of the class loader built into the virtual machine is searched.

If that fails, then the method will invoke findResource(String) to find the resource. The resource name specified as an input can be relative or absolute to the classpath.

It returns an URL object for reading the resource, or null if the resource could not be found or if the invoker doesn't have adequate privileges to return the resource.

It's important to note that Java loads resources from the classpath.

Finally, resource loading in Java is considered location-independent as it doesn't matter where the code is running as long as the environment is set to find the resources.

6. Context Classloaders

In general, context class loaders provide an alternative method to the class-loading delegation scheme introduced in J2SE.

Like we've learned before, classloaders in a JVM follow a hierarchical model such that every class loader has a single parent with the exception of the bootstrap class loader.

However, sometimes when JVM core classes need to dynamically load classes or resources provided by application developers, we might encounter a problem.

For example, in JNDI the core functionality is implemented by bootstrap classes in rt.jar. But these JNDI classes may load JNDI providers implemented by independent vendors (deployed in the application classpath). This scenario calls for the bootstrap class loader (parent class loader) to load a class visible to application loader (child class loader).

J2SE delegation doesn't work here and to get around this problem, we need to find alternative ways of class loading. And it can be achieved using thread context loaders.

The java.lang.Thread class has a method getContextClassLoader() that returns the ContextClassLoader for the particular thread. The ContextClassLoader is provided by the creator of the thread when loading resources and classes.

If the value isn't set, then it defaults to the class loader context of the parent thread.

7. Conclusion

Class loaders are essential to execute a Java program. We've provided a good introduction as part of this article.

우리는 다른 유형의 클래스 로더, 즉 부트 스트랩, 확장 및 시스템 클래스 로더에 대해 이야기했습니다. 부트 스트랩은 이들 모두의 부모 역할을하며 JDK 내부 클래스를로드합니다. 반면 확장과 시스템은 각각 Java 확장 디렉토리와 클래스 경로에서 클래스를로드합니다.

그런 다음 클래스 로더가 작동하는 방식에 대해 이야기하고 위임, 가시성 및 고유성과 같은 일부 기능에 대해 논의한 다음 사용자 지정 로더를 만드는 방법에 대한 간략한 설명을 설명했습니다. 마지막으로 Context 클래스 로더에 대한 소개를 제공했습니다.

항상 그렇듯이 코드 샘플은 GitHub에서 찾을 수 있습니다.