0

5 月 25 日金曜日、グリニッジ標準時 16:00 頃にゼロから書き直しました

(コードがよりきれいになり、バグを再現できるようになり、質問がより明確になりました)

元の問題:ネット経由でクライアントからファイルを受け取り、URLClassLoader を介してローカルに保存された .jar ファイルからロードされる特定のクラスでそれらを処理するために必要なサーバー アプリを作成しています。ほとんどすべてが正常に動作しますが、これらの jar ファイルは (サーバー アプリを再起動せずに) ホット スワップされ、ホットフィックスが適用されます。ロードされると、ClassFormatError「切り捨てられたクラス」または「末尾の余分なバイト」に関するコメントとともにスローされます。それは当然のことですが、アプリケーション全体が不安定になり、その後奇妙な動作を始めます - これらのClassFormatError例外が発生し続けますURLClassLoader の新しいインスタンスを使用し、別のアプリ スレッドで発生したにもかかわらず、更新された同じ jar からクラスを再度ロードしようとすると。

アプリは Debian Squeeze 6.0.3/Java 1.4.2 で実行およびコンパイルされています。移行は私の力ではできません。

アプリの動作を模倣し、問題を大まかに説明する簡単なコードを次に示します。

1) メインアプリとクライアントごとのスレッドのクラス:

package BugTest;

public class BugTest 
{
  //This is a stub of "client" class, which is created upon every connection in real app
  public static class clientThread extends Thread
    {
    private JarLoader j = null;
    public void run()
      {
        try 
          {
          j = new JarLoader("1.jar","SamplePlugin.MyMyPlugin","SampleFileName");
          j.start();
          }
        catch(Exception e)
          {
          e.printStackTrace();
          }
      }
    }

  //Main server thread; for test purposes we'll simply spawn new clients twice a second.
  public static void main(String[] args)
    {
    BugTest bugTest = new BugTest();
    long counter = 0;        
    while(counter < 500)
        {
        clientThread My = null;
        try
            {
            System.out.print(counter+") "); counter++;
            My = new clientThread();
            My.start();
            Thread.currentThread().sleep(500);
            }
        catch(Exception e)
            {
            e.printStackTrace();
            }
        }
    }
}

2) JarLoader - .jar からクラスをロードするためのラッパーで、Thread を拡張します。ここでは、特定のインターフェース a を実装するクラスをロードします。

package BugTest;

import JarPlugin.IJarPlugin;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class JarLoader extends Thread
{
  private String jarDirectory = "jar/";
  private IJarPlugin Jar;
  private String incomingFile = null;

  public JarLoader(String JarFile, String JarClass, String File) 
         throws FileNotFoundException, MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException
    {
    File myjarfile = new File(jarDirectory);
    myjarfile=new File(myjarfile,JarFile);
    if (!myjarfile.exists())
      throw new FileNotFoundException("Jar File Not Found!");
    URLClassLoader ucl = new URLClassLoader(new URL[]{myjarfile.toURL()});
    Class JarLoadedClass =ucl.loadClass(JarClass);
    // ^^ The aforementioned ClassFormatError happens at that line ^^
    Jar = (IJarPlugin) JarLoadedClass.newInstance();
    this.setDaemon(false);
    incomingFile = File
    }

  public void run()
    {
    Jar.SetLogFile("log-plug.txt");
    Jar.StartPlugin("123",incomingFile);
    }
}

3) IJarPlugin - プラグ可能な .jar のシンプルなインターフェース:

package JarPlugin;

public interface IJarPlugin 
{
  public void StartPlugin(String Id, String File);
  public void SetLogFile(String LogFile);
}

4) 実際のプラグイン:

package SamplePlugin;
import JarPlugin.IJarPlugin;

public class MyMyPlugin implements IJarPlugin
{
   public void SetLogFile(String File)
    {
    System.out.print("This is the first plugin: ");
    }
  public void StartPlugin(String Id, String File)
    {
    System.out.println("SUCCESS!!! Id: "+Id+",File: "+File);
    }
}

バグを再現するには、同じクラス名を使用していくつかの異なる .jar をコンパイルする必要があります。唯一の違いは、"This is the Nth plugin:" の番号です。次に、メイン アプリケーションを起動し、ロードされた「1.jar」という名前のプラグイン ファイルを他の .jar にすばやく置き換え、ホットスワップを模倣して元に戻します。繰り返しClassFormatErrorますが、ある時点で予想されることですが、jar が完全にコピーされた場合 (および破損していない場合) にも発生し続け、そのファイルをロードしようとするクライアント スレッドを事実上強制終了します。このサイクルから抜け出す唯一の方法は、プラグインを別のものに置き換えることです。本当に奇妙に思えます。

実際の原因:

コードをさらに単純化し、clientThreadクラスを取り除き、インスタンス化してループJarLoader内で開始するだけで、すべてが明確になりました。がスローされると、スタック トレースが出力されるだけでなく、実際に JVM 全体がクラッシュしました (コード 1 で終了)。理由は今思うほど明白ではありません (少なくとも私にはそうではありませんでした): extends , not . したがって、キャッチされない例外/エラーのためにJVMは通過し、JVMは終了しますが、別の生成された(クライアント)スレッドからエラーを引き起こしたスレッドを生成したため、そのスレッドのみがクラッシュしました。Linux が Java スレッドを処理する方法が原因だと思いますが、よくわかりません。whilemainClassFormatErrorClassFormatErrorErrorExceptioncatch(Exception E)

(その場しのぎの)解決策:

キャッチされなかったエラーの原因が判明したので、「clientThread」内でキャッチしてみました。それはうまくいきました(スタックトレースの出力を削除して、自分のメッセージを出力しました)が、主な問題はまだ存在しClassFormatErrorていました.問題の.jarを置き換えるか削除するまで、. そこで、ある種のキャッシングが原因である可能性があると推測し、これを clientThreadtryブロックに追加して、URLClassLoader 参照の無効化とガベージ コレクションを強制しました。

catch(Error e)
  {
  System.out.println("Aw, an error happened.");
  j=null;
  System.gc();
  } 

驚くことに、うまくいくようです!エラーは 1 回だけ発生し、ファイル クラスは正常にロードされるようになりました。しかし、私は仮説を立てたばかりで、実際の原因を理解していないので、まだ心配しています.今は動作しますが、後ではるかに複雑なコード内で動作するという保証はありません.

では、Java をより深く理解している人は、本当の原因は何かを教えてくれますか、少なくとも方向性を示してくれますか? 既知のバグか、予想される動作かもしれませんが、すでに複雑すぎて自分で理解することはできません。私はまだ初心者です。そして、GC の強制に本当に頼ることができますか?

4

0 に答える 0