이 기사의 내용은 JVM 사용자 정의 클래스 로더가 지정된 classPath 아래의 모든 클래스와 jar를 로드하는 방법에 대한 것입니다. 필요한 친구가 참고할 수 있기를 바랍니다.
1. JVM의 클래스 로더 유형
Java Virtual Machine의 관점에서 보면 클래스 로더는 시작 클래스 로더와 기타 클래스 로더 두 가지뿐입니다.
1. 부트 클래스 로더(Boostrap ClassLoader): 이는 C++로 구현되며 주로 JAVA_HOME/lib 디렉토리 또는 -Xbootclasspath 옵션으로 지정된 jar 패키지의 핵심 API를 로드하는 일을 담당합니다.
2. 기타 클래스 로더: Java로 구현되며 해당 클래스 객체는 메소드 영역에서 찾을 수 있습니다. 이는 여러 로더로 세분화됩니다(
a). 확장 ClassLoader: JAVA_HOME/lib/ext 디렉토리에 있는 파일을 로드하거나 -Djava.ext.dirs 시스템 변수로 지정됩니다. 경로의 모든 클래스 라이브러리(jar)에 대해 , 개발자는 확장 클래스 로더를 직접 사용할 수 있습니다. java.ext.dirs 시스템 변수에 지정된 경로는 System.getProperty("java.ext.dirs")를 통해 확인할 수 있습니다.
b) 애플리케이션 ClassLoader: java -classpath 또는 -Djava.class.path가 가리키는 디렉토리에 클래스 및 jar 패키지를 로드하는 역할을 합니다. 개발자는 이 클래스 로더를 직접 사용할 수 있습니다. 이는 사용자 정의 클래스 로더가 지정되지 않은 경우 프로그램의 기본 로더입니다.
c) 사용자 정의 클래스 로더(User ClassLoader): 프로그램 실행 중에 클래스 파일은 java.lang.ClassLoader의 하위 클래스를 통해 동적으로 로드되며, 이는 Java의 동적 실시간 클래스 로딩 기능을 반영합니다.
이 네 가지 클래스 로더의 계층 관계는 아래 그림과 같습니다.
2. 클래스 로더를 커스터마이즈해야 하는 이유
동일한 이름을 가진 클래스를 구별하려면: Tomcat 애플리케이션 서버에 배포된 독립 애플리케이션이 많고 클래스가 많다고 가정합니다. 이름은 같지만 버전이 다릅니다. 다양한 버전의 클래스를 구별하려면 물론 각 애플리케이션에 자체 독립적인 클래스 로더가 있어야 합니다. 그렇지 않으면 어떤 버전이 사용되는지 구별하는 것이 불가능합니다.
클래스 라이브러리 공유: 각 웹 애플리케이션은 tomcat에서 자체 jar 버전을 사용할 수 있습니다. 그러나 서로 공유할 수 있는 Servlet-api.jar, Java 기본 패키지 및 사용자 정의 추가 Java 클래스 라이브러리와 같은 것들이 있습니다.
Enhance 클래스: 클래스 로더는 Class를 로드할 때 클래스를 다시 작성하고 덮어쓸 수 있으며, 이 동안 클래스 기능이 향상될 수 있습니다. 예를 들어 javassist를 사용하여 클래스에 함수를 추가 및 수정하거나 관점 지향 프로그래밍, 디버깅 및 기타 원칙에 사용되는 동적 프록시를 추가합니다.
핫 교체: 애플리케이션이 실행되는 동안 소프트웨어를 업그레이드할 수 있으며 애플리케이션을 다시 시작할 필요가 없습니다. 예를 들어 toccat 서버에서 JSP 업데이트 및 교체
3. 사용자 정의 클래스 로더
3.1 ClassLoader 사용자 정의 클래스 로더 관련 메소드 설명 구현
사용자 정의 클래스 로더를 구현하려면 먼저 ClassLoader를 상속해야 합니다. ClassLoader 클래스는 클래스의 객체를 로드하는 추상 클래스입니다. 사용자 정의 ClassLoader의 메소드 중 적어도 세 가지(loadClass, findClass, DefineClass)를 알아야 합니다.
public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);
protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name); }
protected final Class<?> defineClass(String name, byte[] b, int off, int len)throws ClassFormatError {return defineClass(name, b, off, len, null); }
loadClass: JVM은 클래스를 로드할 때 ClassLoader의 loadClass() 메서드를 통해 클래스를 로드합니다. loadClass는 상위 위임 모드를 사용합니다. 상위 위임 모드를 변경하려면 loadClass를 수정하여 클래스가 로드되는 방식을 변경할 수 있습니다. 여기서는 부모 위임 모델에 대해 자세히 설명하지 않습니다.
findClass: ClassLoader는 findClass() 메서드를 통해 클래스를 로드합니다. 사용자 정의 클래스 로더는 지정된 경로의 파일, 바이트 스트림 등과 같은 필수 클래스를 로드하기 위해 이 메소드를 구현합니다.
DefinedClass: DefinedClass는 Class 파일에 전달된 바이트 배열을 호출하여 메소드 영역에 Class 객체를 생성할 수 있습니다. 이는 findClass가 클래스 로딩 기능을 구현한다는 의미입니다.
실제 모습을 보려면 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; } }
소스 코드 설명...
/** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * * @param name * The <a href="#name">binary name</a> of the class * * @param resolve * If <tt>true</tt> then resolve the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found */
번역은 아마도 : 지정된 바이너리 이름을 사용하여 클래스를 로드합니다. 이 메소드의 기본 구현은 다음과 같습니다. 다음 순서로 클래스를 찾습니다. findLoadedClass(String) 메소드를 호출하여 이 클래스가 로드되었는지 확인합니다. 상위 로더가 다음과 같은 경우 loadClass(String) 메소드를 호출합니다. Null인 경우, 클래스 로더는 가상 머신의 내장 로더를 로드하고 findClass(String).) 메소드를 호출하여 위 단계에 따라 해당 클래스를 성공적으로 찾았으며 이에 의해 수신된 해결 매개변수의 값은 다음과 같습니다. 메소드가 true인 경우, 클래스를 처리하기 위해 해결클래스(클래스) 메소드가 호출됩니다. ClassLoader의 하위 클래스는 이 메서드 대신 findClass(String)를 재정의하는 것이 더 좋습니다. 재정의되지 않는 한 이 메서드는 기본적으로 전체 로딩 프로세스에서 동기식(스레드로부터 안전함)입니다.
resolveClass:Class载入必须链接(link),链接指的是把单一的Class加入到有继承关系的类树中。这个方法给Classloader用来链接一个类,如果这个类已经被链接过了,那么这个方法只做一个简单的返回。否则,这个类将被按照 Java™规范中的Execution描述进行链接。
3.2 自定义类加载器实现
按照3.1的说明,继承ClassLoader后重写了findClass方法加载指定路径上的class。先贴上自定义类加载器。
package com.chenerzhu.learning.classloader; import java.nio.file.Files; import java.nio.file.Paths; /** * @author chenerzhu * @create 2018-10-04 10:47 **/ public class MyClassLoader extends ClassLoader { private String path; public MyClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] result = getClass(name); if (result == null) { throw new ClassNotFoundException(); } else { return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] getClass(String name) { try { return Files.readAllBytes(Paths.get(path)); } catch (Exception e) { e.printStackTrace(); } return null; } }
以上就是自定义的类加载器了,实现的功能是加载指定路径的class。再看看如何使用。
package com.chenerzhu.learning.classloader; import org.junit.Test; /** * Created by chenerzhu on 2018/10/4. */ public class MyClassLoaderTest { @Test public void testClassLoader() throws Exception { MyClassLoader myClassLoader = new MyClassLoader("src/test/resources/bean/Hello.class"); Class clazz = myClassLoader.loadClass("com.chenerzhu.learning.classloader.bean.Hello"); Object obj = clazz.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader()); } }
首先通过构造方法创建MyClassLoader对象myClassLoader,指定加载src/test/resources/bean/Hello.class路径的Hello.class(当然这里只是个例子,直接指定一个class的路径了)。然后通过myClassLoader方法loadClass加载Hello的Class对象,最后实例化对象。以下是输出结果,看得出来实例化成功了,并且类加载器使用的是MyClassLoader。
com.chenerzhu.learning.classloader.bean.Hello@2b2948e2 com.chenerzhu.learning.classloader.MyClassLoader@335eadca
四、类Class卸载
JVM中class和Meta信息存放在PermGen space区域(JDK1.8之后存放在MateSpace中)。如果加载的class文件很多,那么可能导致元数据空间溢出。引起java.lang.OutOfMemory异常。对于有些Class我们可能只需要使用一次,就不再需要了,也可能我们修改了class文件,我们需要重新加载 newclass,那么oldclass就不再需要了。所以需要在JVM中卸载(unload)类Class。
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):
该类所有的实例都已经被GC。
该类的java.lang.Class对象没有在任何地方被引用。
加载该类的ClassLoader实例已经被GC。
很容易理解,就是要被卸载的类的ClassLoader实例已经被GC并且本身不存在任何相关的引用就可以被卸载了,也就是JVM清除了类在方法区内的二进制数据。
JVM自带的类加载器所加载的类,在虚拟机的生命周期中,会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象。因此这些Class对象始终是可触及的,不会被卸载。而用户自定义的类加载器加载的类是可以被卸载的。虽然满足以上三个条件Class可以被卸载,但是GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。
五、JVM自定义类加载器加载指定classPath下的所有class及jar
经过以上几个点的说明,现在可以实现JVM自定义类加载器加载指定classPath下的所有class及jar了。这里没有限制class和jar的位置,只要是classPath路径下的都会被加载进JVM,而一些web应用服务器加载是有限定的,比如tomcat加载的是每个应用classPath+“/classes”加载class,classPath+“/lib”加载jar。以下就是代码啦...
package com.chenerzhu.learning.classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @author chenerzhu * @create 2018-10-04 12:24 **/ public class ClassPathClassLoader extends ClassLoader{ private static Map<String, byte[]> classMap = new ConcurrentHashMap<>(); private String classPath; public ClassPathClassLoader() { } public ClassPathClassLoader(String classPath) { if (classPath.endsWith(File.separator)) { this.classPath = classPath; } else { this.classPath = classPath + File.separator; } preReadClassFile(); preReadJarFile(); } public static boolean addClass(String className, byte[] byteCode) { if (!classMap.containsKey(className)) { classMap.put(className, byteCode); return true; } return false; } /** * 这里仅仅卸载了myclassLoader的classMap中的class,虚拟机中的 * Class的卸载是不可控的 * 自定义类的卸载需要MyClassLoader不存在引用等条件 * @param className * @return */ public static boolean unloadClass(String className) { if (classMap.containsKey(className)) { classMap.remove(className); return true; } return false; } /** * 遵守双亲委托规则 */ @Override protected Class<?> findClass(String name) { try { byte[] result = getClass(name); if (result == null) { throw new ClassNotFoundException(); } else { return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] getClass(String className) { if (classMap.containsKey(className)) { return classMap.get(className); } else { return null; } } private void preReadClassFile() { File[] files = new File(classPath).listFiles(); if (files != null) { for (File file : files) { scanClassFile(file); } } } private void scanClassFile(File file) { if (file.exists()) { if (file.isFile() && file.getName().endsWith(".class")) { try { byte[] byteCode = Files.readAllBytes(Paths.get(file.getAbsolutePath())); String className = file.getAbsolutePath().replace(classPath, "") .replace(File.separator, ".") .replace(".class", ""); addClass(className, byteCode); } catch (IOException e) { e.printStackTrace(); } } else if (file.isDirectory()) { for (File f : file.listFiles()) { scanClassFile(f); } } } } private void preReadJarFile() { File[] files = new File(classPath).listFiles(); if (files != null) { for (File file : files) { scanJarFile(file); } } } private void readJAR(JarFile jar) throws IOException { Enumeration<JarEntry> en = jar.entries(); while (en.hasMoreElements()) { JarEntry je = en.nextElement(); je.getName(); String name = je.getName(); if (name.endsWith(".class")) { //String className = name.replace(File.separator, ".").replace(".class", ""); String className = name.replace("\\", ".") .replace("/", ".") .replace(".class", ""); InputStream input = null; ByteArrayOutputStream baos = null; try { input = jar.getInputStream(je); baos = new ByteArrayOutputStream(); int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } addClass(className, baos.toByteArray()); } catch (Exception e) { e.printStackTrace(); } finally { if (baos != null) { baos.close(); } if (input != null) { input.close(); } } } } } private void scanJarFile(File file) { if (file.exists()) { if (file.isFile() && file.getName().endsWith(".jar")) { try { readJAR(new JarFile(file)); } catch (IOException e) { e.printStackTrace(); } } else if (file.isDirectory()) { for (File f : file.listFiles()) { scanJarFile(f); } } } } public void addJar(String jarPath) throws IOException { File file = new File(jarPath); if (file.exists()) { JarFile jar = new JarFile(file); readJAR(jar); } } }
如何使用的代码就不贴了,和3.2节自定义类加载器的使用方式一样。只是构造方法的参数变成classPath了,篇末有代码。当创建MyClassLoader对象时,会自动添加指定classPath下面的所有class和jar里面的class到classMap中,classMap维护className和classCode字节码的关系,只是个缓冲作用,避免每次都从文件中读取。自定义类加载器每次loadClass都会首先在JVM中找是否已经加载className的类,如果不存在就会到classMap中取,如果取不到就是加载错误了。
六、最后
至此,JVM自定义类加载器加载指定classPath下的所有class及jar已经完成了。这篇博文花了两天才写完,在写的过程中有意识地去了解了许多代码的细节,收获也很多。本来最近仅仅是想实现Quartz控制台页面任务添加支持动态class,结果不知不觉跑到类加载器的坑了,在此也趁这个机会总结一遍。当然以上内容并不能保证正确,所以希望大家看到错误能够指出,帮助我更正已有的认知,共同进步。。。
本文的代码已经上传github:https://github.com/chenerzhu/learning/tree/master/classloader
위 내용은 JVM 사용자 정의 클래스 로더는 지정된 classPath 아래의 모든 클래스와 jar를 어떻게 로드합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

신흥 기술은 위협을 일으키고 Java의 플랫폼 독립성을 향상시킵니다. 1) Docker와 같은 클라우드 컴퓨팅 및 컨테이너화 기술은 Java의 플랫폼 독립성을 향상 시키지만 다양한 클라우드 환경에 적응하도록 최적화되어야합니다. 2) WebAssembly는 Graalvm을 통해 Java 코드를 컴파일하여 플랫폼 독립성을 확장하지만 성능을 위해 다른 언어와 경쟁해야합니다.

다른 JVM 구현은 플랫폼 독립성을 제공 할 수 있지만 성능은 약간 다릅니다. 1. OracleHotspot 및 OpenJDKJVM 플랫폼 독립성에서 유사하게 수행되지만 OpenJDK에는 추가 구성이 필요할 수 있습니다. 2. IBMJ9JVM은 특정 운영 체제에서 최적화를 수행합니다. 3. Graalvm은 여러 언어를 지원하며 추가 구성이 필요합니다. 4. AzulzingJVM에는 특정 플랫폼 조정이 필요합니다.

플랫폼 독립성은 여러 운영 체제에서 동일한 코드 세트를 실행하여 개발 비용을 줄이고 개발 시간을 단축시킵니다. 구체적으로, 그것은 다음과 같이 나타납니다. 1. 개발 시간을 줄이면 하나의 코드 세트 만 필요합니다. 2. 유지 보수 비용을 줄이고 테스트 프로세스를 통합합니다. 3. 배포 프로세스를 단순화하기위한 빠른 반복 및 팀 협업.

Java'SplatformIndenceFacilitatesCodereScoderEByWatHeAveringByTeCodetOrunonAnyPlatformwitHajvm.1) DevelopersCanwriteCodeOnceforConsentEStentBehaviorAcRossPlatforms.2) MAINTENDUCEDSCODEDOES.3) LIBRRIESASHSCORAREDERSCRAPERAREDERSPROJ

Java 응용 프로그램의 플랫폼 별 문제를 해결하려면 다음 단계를 수행 할 수 있습니다. 1. Java의 시스템 클래스를 사용하여 시스템 속성을보고 실행중인 환경을 이해합니다. 2. 파일 클래스 또는 java.nio.file 패키지를 사용하여 파일 경로를 처리하십시오. 3. 운영 체제 조건에 따라 로컬 라이브러리를로드하십시오. 4. visualVM 또는 JProfiler를 사용하여 크로스 플랫폼 성능을 최적화하십시오. 5. 테스트 환경이 Docker Containerization을 통해 생산 환경과 일치하는지 확인하십시오. 6. githubactions를 사용하여 여러 플랫폼에서 자동 테스트를 수행하십시오. 이러한 방법은 Java 응용 프로그램에서 플랫폼 별 문제를 효과적으로 해결하는 데 도움이됩니다.

클래스 로더는 통합 클래스 파일 형식, 동적로드, 부모 위임 모델 및 플랫폼 독립적 인 바이트 코드를 통해 다른 플랫폼에서 Java 프로그램의 일관성과 호환성을 보장하고 플랫폼 독립성을 달성합니다.

Java 컴파일러가 생성 한 코드는 플랫폼 독립적이지만 궁극적으로 실행되는 코드는 플랫폼 별입니다. 1. Java 소스 코드는 플랫폼 독립적 인 바이트 코드로 컴파일됩니다. 2. JVM은 바이트 코드를 특정 플랫폼의 기계 코드로 변환하여 크로스 플랫폼 작동을 보장하지만 성능이 다를 수 있습니다.

멀티 스레딩은 프로그램 대응 성과 리소스 활용을 향상시키고 복잡한 동시 작업을 처리 할 수 있기 때문에 현대 프로그래밍에서 중요합니다. JVM은 스레드 매핑, 스케줄링 메커니즘 및 동기화 잠금 메커니즘을 통해 다양한 운영 체제에서 멀티 스레드의 일관성과 효율성을 보장합니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SublimeText3 영어 버전
권장 사항: Win 버전, 코드 프롬프트 지원!

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

mPDF
mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경
