2012-06-30 15 views
5

Tôi đã chơi đùa với classLoaders trong Java và nhận thấy một điều kỳ lạ. Nếu một classLoader nạp một lớp từ một lọ, jar này bị khóa vô thời hạn ngay cả khi bạn không cân nhắc classLoader của bạn.Vấn đề tiến thoái lưỡng nan về Java classLoader với các khóa bị khóa

Trong ví dụ dưới đây, bình chứa một lớp có tên là HelloWorld. Những gì tôi làm là cố gắng tải các lớp chứa trong jar thông qua một classLoader mà thêm jar động. Nếu bạn đặt skip thành true và không gọi Class.forName, bạn có thể xóa bình nhưng nếu bạn không bỏ qua và ngay cả khi bạn không trả lời classLoader (classLoader = null), không thể xóa bình cho đến khi JVM thoát.

Tại sao lại như vậy?

PS: Tôi đang sử dụng java 6 và mã là rất dài dòng cho mục đích thử nghiệm

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

Bạn có muốn giải pháp hoặc giải thích không ? – esej

+1

@esej Tôi đã tìm thấy cả hai, quan tâm để kiểm tra câu trả lời của tôi và chia sẻ ý kiến ​​của bạn? –

Trả lời

8

Tôi đã tìm thấy câu trả lời và giải pháp thay thế.

Dựa trên điều này article và liên quan tuyệt vời này article, thói quen xấu là sử dụng Class.forName(className, true, classLoader) vì nó giữ lớp được lưu trong bộ nhớ vô thời hạn.

Giải pháp là sử dụng classLoader.loadClass(clasName) thay vào đó, sau đó một lần xong, unreference các classLoader và gọi garbage collector sử dụng:

classLoader = null; 
System.gc(); 

Hy vọng điều này giúp những người khác! :)

Background Thông tin:

dự án của tôi là một Complexe một: chúng tôi đã có một máy chủ GWT đóng vai trò như một khách hàng RMI để máy chủ khác. Vì vậy, để tạo các cá thể, GWT cần tải xuống các lớp từ máy chủ và tải chúng. Sau đó, GWT sẽ gửi lại cá thể đến máy chủ để duy trì chúng trong cơ sở dữ liệu bằng Hibernate. Để hỗ trợ triển khai nóng, chúng tôi đã chọn tải lớp động, trong đó người dùng sẽ tải lên một bình và thông báo cho máy chủ sẽ tải các lớp từ đó và hiển thị chúng cho máy chủ GWT

+0

Lưu ý rằng điều này chỉ hoạt động nếu các lớp * tất cả * được nạp bởi trình nạp lớp này và * tất cả * các cá thể của bất kỳ lớp nào trong số này cũng không được tham chiếu. Miễn là thậm chí có một thể hiện của một lớp như vậy, trình nạp lớp sẽ được giữ trong bộ nhớ (vì bạn có thể gọi 'instance.getClass(). GetClassLoader()'). –

+0

Tuyệt vời, tất cả các trường hợp và lớp học cần phải được derefernced - và họ có thể kết thúc trong PermGen. Tôi nghĩ người ta nên lưu ý rằng trong Java 7 chúng ta có 'URLClassLoader # close()' - nên được sử dụng. Tôi có những ký ức mơ hồ mơ hồ từ việc vượt qua các classpath và tải các tập tin vào bộ nhớ để tải các lớp từ ... không bao giờ thực sự thực sự giải quyết được vấn đề. Classloaders là động vật kỳ lạ. – esej

+0

@esej Bạn có thể muốn tạo một lớp con của ClassLoader, mở thủ công tệp và đọc nội dung của nó thành một mảng byte và sau đó gọi 'ClassLoader # defineClass (Tên chuỗi, byte [] b, int off, int len)'? Điều này thực sự nên làm việc. –

2

Trong Java 7 URLClassLoader có một phương pháp #close() điều này khắc phục này.

+0

Tôi đã đề cập đến tôi đang sử dụng java 6 và điều này không thể thay đổi ngay bây giờ: D –

+0

Java 6 là EoL thực sự sớm. Bạn có thể thử xóa tất cả các tham chiếu đến trình nạp lớp và buộc hoàn thành GC _and_ đang chạy finalization. Nhưng điều này thật khủng khiếp. Hoặc bạn có thể viết trình nạp lớp jar của riêng bạn có phương thức đóng. –

+1

Tôi đã tìm thấy một giải pháp sử dụng java 6, chăm sóc để kiểm tra câu trả lời của tôi và chia sẻ ý kiến ​​của bạn? –