2012-06-30 7 views
5

Java에서 classLoaders로 놀고 있었고 이상한 점을 발견했습니다. classLoader가 jar에서 클래스를로드하면이 jar는 classLoader를 참조 해제하더라도 무한정 잠겨집니다.잠긴 항아리가있는 Java classLoader 딜레마

아래 예제에서 jar에는 HelloWorld라는 클래스가 있습니다. 내가하는 일은 jar를 동적으로 추가하는 classLoader를 통해 jar에 포함 된 클래스를로드하려고 시도하는 것입니다. skiptrue으로 설정하고 Class.forName을 호출하지 않으면 jar를 삭제할 수 있습니다. 건너 뛰지 않고 classLoader (classLoader = null)을 참조하지 않아도 JVM은 종료 될 때까지 삭제할 수 없습니다.

왜 그렇습니까?

PS : I 자바 6을 사용하고 상기 코드는 테스트 목적 자바 7

package loader; 

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class TestClassLoader { 

    private URLClassLoader classLoader; 

    public TestClassLoader() throws MalformedURLException, IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      performFirstCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    public static void main(String[] args) throws IOException { 
     System.out.println("Test started"); 
     TestClassLoader testClassLoader = new TestClassLoader(); 
     System.out.println("Bye!"); 
    } 

    public void performFirstCheck() throws IOException { 
     System.out.println("Checking class HelloWorld does not exist"); 
     if (!checkClassFound(TestClassLoader.class.getClassLoader(), false)) { 
      System.out.println("Deleting jar"); 
      deleteJar(); 
      System.out.println("First Check SUCCESS"); 
      performSecondCheck(); 
     } else { 
      System.out.println("First Check FAILED"); 
     } 
    } 

    private void performSecondCheck() throws IOException { 
     System.out.println("Copying jar"); 
     if (copyJar()) { 
      System.out.println("Copying SUCCESS"); 
      createClassLoaderAndCheck(); 
     } else { 
      System.out.println("Copying FAILED"); 
     } 
    } 

    private void createClassLoaderAndCheck() throws MalformedURLException { 
     System.out.println("Creating classLoader"); 
     createClassLoader(); 
     System.out.println("Checking class HelloWorld exist"); 
     if (checkClassFound(classLoader, true)) { 
      System.out.println("Second Check SUCCESS"); 
        classLoader = null; 
      System.out.println("Deleting jar"); 
      if (deleteJar()) { 
       System.out.println("Deleting SUCCESS"); 
      } else { 
       System.out.println("Deleting FAILED"); 
      } 
     } else { 
      System.out.println("Second Check FAILED"); 
     } 
    } 

    public void createClassLoader() throws MalformedURLException { 
     URL[] urls = new URL[1]; 
     File classFile = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     urls[0] = classFile.toURI().toURL(); 
     classLoader = new URLClassLoader(urls); 
    } 

    public boolean checkClassFound(ClassLoader classLoader, boolean skip) { 
     if (skip) { 
      System.out.println("Skiping class loading"); 
      return true; 
     } else { 
      try { 
       Class.forName("HelloWorld", true, classLoader); 
       return true; 
      } catch (ClassNotFoundException e) { 
       return false; 
      } 
     } 
    } 

    public URLClassLoader getClassLoader() { 
     return classLoader; 
    } 

    public boolean copyJar() throws IOException { 
     File sourceJar = new File("C:\\Users\\Adel\\Desktop\\Folder\\classes.jar"); 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     if (destJar.exists()) { 
      return false; 
     } else { 
      FileInputStream finput = new FileInputStream(sourceJar); 
      FileOutputStream foutput = new FileOutputStream(destJar); 
      byte[] buf = new byte[1024]; 
      int len; 
      while ((len = finput.read(buf)) > 0) { 
       foutput.write(buf, 0, len); 
      } 
      finput.close(); 
      foutput.close(); 
      return true; 
     } 
    } 

    public boolean deleteJar() { 
     File destJar = new File("C:\\Users\\Adel\\Desktop\\classes.jar"); 
     return destJar.delete(); 
    } 

} 
+0

해결 방법이나 설명을 원하십니까? ? – esej

+1

@esej 이미 내 답변을 확인하고 의견을 공유하는 두 가지를 찾았습니까? –

답변

8

답변 및 해결 방법을 찾았습니다.

article과 놀랍게도 관련 article을 기반으로하면 메모리에 캐시 된 클래스를 무기한으로 유지하기 때문에 Class.forName(className, true, classLoader)을 사용하는 것은 나쁜 습관입니다.

classLoader = null; 
System.gc(); 

희망이 다른 사람을 도움 :

이 솔루션은 classLoader unreference classLoader.loadClass(clasName) 대신, 다음 번에 완료 사용하여 가비지 콜렉터를 호출하는 것이 었습니다! :)

배경 정보 :

내 프로젝트는 complexe 하나 : 우리가 다른 서버로 RMI 클라이언트 역할을하는 GWT 서버를했다. 따라서 인스턴스를 생성하기 위해 GWT는 서버에서 클래스를 다운로드하여로드해야했습니다. 나중에 GWT는 Hibernate를 사용하여 인스턴스를 데이터베이스에 유지하기 위해 인스턴스를 서버에 다시 보냅니다. 핫 배포를 지원하기 위해 사용자가 항아리를 업로드하고 GWT 서버에서 사용할 수있는 클래스를로드 할 서버에 알리는 동적 클래스로드를 선택했습니다.

+0

이 클래스 로더에 의해로드 된 * all * 클래스와 이들 클래스의 * all * 인스턴스도 참조 해제되지 않은 경우에만 작동한다는 점에 유의하십시오. 이러한 클래스의 인스턴스가 하나만 존재하는 한, 클래스 로더는 (instance.getClass(). getClassLoader()를 호출 할 수 있기 때문에) 메모리에 유지됩니다. –

+0

우수한, 모든 인스턴스와 클래스를 derefernced해야합니다 - 그리고 그들은 PermGen에 끝날 수도 있습니다. 나는 Java 7에서 URLClassLoader # close()를 사용해야한다는 것을 알아야한다. classpath를 가로 지르고 클래스를로드하기 위해 파일을 메모리에로드하는 것으로부터 나는 막연한 악몽 적 추억을 가지고 있습니다 ... 실제로는 실제로 문제를 해결하지 못했습니다. Classloader는 이상한 동물입니다. – esej

+0

@esej 아마도 ClassLoader의 하위 클래스를 만들고, 파일을 수동으로 열고 바이트 배열로 내용을 읽은 다음 ClassLoader # defineClass (String name, byte [] b, int off, int len)를 호출하는 것을 의미할까요? 이것은 실제로 작동해야합니다. –

2

URLClassLoader 이것을 고정하는 #close() 방법을 가지고있는 아주 장황.

+0

나는 자바 6을 사용하고 있으며 이것은 현재 바뀔 수 없다고 언급했다 : D –

+0

자바 6은 정말로 곧 EoL이다. 클래스 로더에 대한 모든 참조를 제거하고 완전한 GC _and_ 실행 종료를 시도 할 수 있습니다. 그러나 이것은 끔찍한 일입니다. 또는 가까운 방법이있는 고유 한 jar 클래스 로더를 작성할 수 있습니다. –

+1

나는 자바 6을 사용하는 해결책을 이미 찾았고, 내 대답을 확인하고 의견을 나눌 것을 유의한다. –