序章
お客様のオフィスで Java アプレットに問題が発生しました。アプレットは、定期的にスクリーンショットを取得して画面を記録することを目的としています。署名されており、昇格された特権で実行する必要があります。
私たちが遭遇する問題は次のとおりです。
- Java アプレットがまったく起動しない場合があります。Java コンソールが表示された場合、すぐに消える可能性があります。JVM全体がクラッシュしたように感じます。
- Java アプレットが起動すると、起動後 10 秒程度でクラッシュすることがあります。
- Java アプレットが起動すると、ほとんどの場合、最大 12 秒間続く一時的なフリーズが発生します。
- (アプレットの起動は少し遅いです。)
典型的なケースは、Windows を起動した後、最初の試みでアプレットがロードされないことです。ブラウザを更新/再ログイン/再起動した後 (何が必要なのか完全にはわかりません)、アプレットが起動し、フリーズしてからクラッシュします。3 回目の試行ではクラッシュしなくなりますが、それでもフリーズが発生します。
バックグラウンド
顧客は、おそらく問題に何らかの関係がある Blue Coat Systems HTTP プロキシを持っています。ネットワークは、インターネットへのすべてのトラフィックがプロキシを通過する必要があるように設定されています。たとえばping www.google.com
、ホスト www.google.com を解決できなかったため、タイムアウト後に失敗します。Java と Blue Coat プロキシの問題を説明しているページをいくつか見つけました。これらはあまり役立つ情報ではないと思いますが、プロキシが問題に関係しているという私の信念を強めるものです。
- Java がブルーコート プロキシに解決されない
- GoToAssist、GoToMeeting、GoToWebinar、または GoToMyPC へのアクセスに関する問題
- Salesforce.com へのアクセスに関する問題
- Firefox ブラウザーで RealTimeGraph にアクセスすると、Java エラーが発生します。
プロキシには NTLM 認証が必要です。つまり、ブラウザが初めて通過しようとすると、ユーザー名とパスワードを求めるポップアップが表示されます。Chrome を使用して Google にアクセスしようとしたときのプロキシからの HTTP 応答の一部を次に示します。
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: NTLM TlRMTVNTUAACAAAADAAMADgAAAAFgokC/+XiO/tpmQMAAAAAAAAAAKQApABEAAAABQLODgAAAA9KAEEATQBQAFQASQACAAwASgBBAE0AUABUAEkAAQASAFAASwBIAEsASQBEAEMAMAAxAAQAHgBwAHUAdQBrAGUAcwBrAHUAcwAuAGwAbwBjAGEAbAADADIAcABrAGgAawBpAGQAYwAwADEALgBwAHUAdQBrAGUAcwBrAHUAcwAuAGwAbwBjAGEAbAAFAB4AcAB1AHUAawBlAHMAawB1AHMALgBsAG8AYwBhAGwAAAAAAA==
環境
私は顧客の敷地内にいて、自分のラップトップで問題を自分で体験しました。私のセットアップでは、仮想マシンで Ubuntu Linux 12.04 と Windows 7 を実行しています。ほとんどのテストで Windows と Java 1.6.35 を使用しました。Linux でアプレットを 1 回実行しました (Oracle Java 1.7.0_07 を使用したと思います)。
キャッシュされたJavaアプレットが開発を台無しにすることがあるので、Java設定の「一時ファイルをコンピュータに保持する」ボックスのチェックを外しました。顧客のオフィスでアプレットをデバッグしていたとき、一時ファイルを無効にしていましたが、顧客はおそらく一時ファイルを有効にしており、これらの問題が常に発生しているため、この設定が問題に影響を与えるとは思いません.
問題はブラウザに依存していないようです。IE9、Firefox、Chrome、Opera を試しました。
観察
Windows でアプレットを実行するとき、Eclipse デバッガーをアプレットに接続しました。フリーズしたアプレットの実行を中断し、動作を確認しました。スタックの外観は次のとおりです。「OurOwnClass.doStillMoreSomething」は、実行中の独自のコードの最後の行であり、クラスの新しいインスタンスを作成します。
Thread [AWT-EventQueue-2] (Suspended)
Inet6AddressImpl.lookupAllHostAddr(String) line: not available [native method]
InetAddress$1.lookupAllHostAddr(String) line: not available
InetAddress.getAddressFromNameService(String, InetAddress) line: not available
InetAddress.getAllByName0(String, InetAddress, boolean) line: not available
InetAddress.getAllByName(String, InetAddress) line: not available
InetAddress.getAllByName(String) line: not available
InetAddress.getByName(String) line: not available
Handler(URLStreamHandler).getHostAddress(URL) line: not available
Handler(URLStreamHandler).hostsEqual(URL, URL) line: not available
Handler(URLStreamHandler).sameFile(URL, URL) line: not available
Handler(URLStreamHandler).equals(URL, URL) line: not available
URL.equals(Object) line: not available
JarVerifier$VerifierCodeSource(CodeSource).equals(Object) line: not available
JarVerifier$VerifierCodeSource.equals(Object) line: not available
HashMap<K,V>.getEntry(Object) line: not available
HashMap<K,V>.containsKey(Object) line: not available
HashSet<E>.contains(Object) line: not available
CPCallbackHandler.isTrusted(CodeSource) line: not available
CPCallbackHandler.access$1200(CPCallbackHandler, CodeSource) line: not available
CPCallbackHandler$ChildElement.checkResource(String) line: not available
DeployURLClassPath$JarLoader.checkResource(String, boolean, JarEntry, JarFile, DeployURLClassPath$PathIterator) line: not available
DeployURLClassPath$JarLoader.getResource(String, boolean, DeployURLClassPath$PathIterator) line: not available
DeployURLClassPath.getResource(String, boolean) line: not available
Plugin2ClassLoader$2.run() line: not available
AccessController.doPrivileged(PrivilegedExceptionAction<T>, AccessControlContext) line: not available [native method]
Applet2ClassLoader(Plugin2ClassLoader).findClassHelper(String) line: not available
Applet2ClassLoader.findClass(String, boolean) line: not available
Applet2ClassLoader(Plugin2ClassLoader).loadClass0(String, boolean, boolean) line: not available
Applet2ClassLoader(Plugin2ClassLoader).loadClass(String, boolean, boolean) line: not available
Applet2ClassLoader(Plugin2ClassLoader).loadClass(String, boolean) line: not available
Applet2ClassLoader(ClassLoader).loadClass(String) line: not available
OurOwnClass.doStillMoreSomething(String) line: 701
OurOwnClass.doMoreSomething() line: 671
OurOwnClass.doSometihng() line: 655
OurOwnClass.handleTimer() line: 510
OurOwnClass.actionPerformed(ActionEvent) line: 391
Timer.fireActionPerformed(ActionEvent) line: not available
Timer$DoPostEvent.run() line: not available
InvocationEvent.dispatch() line: not available
EventQueue.dispatchEventImpl(AWTEvent, Object) line: not available
EventQueue.access$400(EventQueue, AWTEvent, Object) line: not available
EventQueue$2.run() line: not available
EventQueue$2.run() line: not available
AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]
AccessControlContext$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: not available
EventQueue.dispatchEvent(AWTEvent) line: not available
EventDispatchThread.pumpOneEventForFilters(int) line: not available
EventDispatchThread.pumpEventsForFilter(int, Conditional, EventFilter) line: not available
EventDispatchThread.pumpEventsForHierarchy(int, Conditional, Component) line: not available
EventDispatchThread.pumpEvents(int, Conditional) line: not available
EventDispatchThread.pumpEvents(Conditional) line: not available
EventDispatchThread.run() line: not available
アプレットがフリーズする数行のコードがありました。スタックは、独自のコード (上記: OurOwnClass.doStillMoreSomething) の最初の行まで同じように見えるため、すべてのフリーズが同じ原因であるように見えました。そして、この問題を引き起こしているすべての行は、新しいオブジェクトを作成していました。これらすべてのケースで、特定のクラスがロードされたのも初めてだったと思いますが、これは確認していません。クラスの 1 つは私たち自身のパブリック クラスでしたが、それらのほとんどは、OurOwnClass$4 のような名前と次のようなコードを持つ匿名クラスでした。
worker = new SwingWorker<Void,Void>() {
@Override
public Void doInBackground() {
// Do stuff.
return null;
}
};
スタックの一番上にある Java メソッドは、Inet6AddressImpl.lookupAllHostAddr でした。デバッガーは、このメソッドがアプレットを提供するホストのホスト名を解決しようとしていることを示しました。アプレット JAR ファイルの完全な URL をどこかで見たと思うので、JVM がアプレット JAR ファイルにアクセスしようとしているような印象を受けました。
分析
私の最善の推測は、クラスローダがロードしようとしているクラスに対して何らかの権限チェックを行う必要があるということです。おそらく、JVM は JAR ファイルが最初にロードされてから更新されたかどうかを確認しようとしていますが、サーバーのホスト名を解決するためにネイティブ メソッドを使用しようとしている間に JVM がハングします。アドレス解決が失敗すると、クラスローダーの実行が戻ります。アプレットはサーバーと正常に通信できる (ブラウザからプロキシ情報を取得する) ため、これはすべて少し奇妙ですが、JVM はここで何をしていてもプロキシを使用していないように感じます。この説明はもっともらしく聞こえますか?
繰り返しになりますが、そもそもクラッシュと、アプレットを起動する際の問題をどのように説明すればよいかわかりません。これらの問題のデバッグに時間を費やしていないため、何が起こっているのかわかりません。今後も顧客のオフィスに行く場合は、hs_err_*.log ファイルを探す必要があるかもしれません。
顧客と同様のセットアップがあれば、デバッグに役立ちます。HTTP プロキシを使用してのみインターネットにアクセスできる環境をセットアップしようとするかもしれませんが、それが Blue Coat プロキシでなければならないかどうかわかりません。また、他の方法(基本、ダイジェスト)ではなく、NTLMプロトコルを使用してプロキシに認証を要求できるかどうかもわかりません。
いずれにせよ、フリーズ (可能であれば、クラッシュと起動の問題) を解消するための助けをいただければ幸いです。これらの問題を回避できるように、コードまたは JAR パッケージを何らかの方法で変更できますか? 役立つ JVM 起動パラメータはありますか? 問題を調査するために私がしなければならないことはまだありますか?
編集
インターネット全体ではなく、ホスト Linux とのみ通信できるように、Windows 仮想マシンのネットワーク設定を変更しました。次に、ホスト Linux で Squid HTTP プロキシをセットアップし、Windows にそれをプロキシとして使用するように指示しました。現在、顧客のオフィスで発生したのと同じような問題が発生していますが、それほど確実ではありません。プロキシに認証を要求させる必要はないようです。
編集 2
シミュレートされた環境を使用し、Wireshark を起動して、Windows ゲストが何をしようとしているのかを確認しました。どうやら、ホストとピンポンのように遊んでいるようです。サーバーの IP を尋ね、「Destination unreachable (Port unreachable)」という応答を返します (タイプ 3、コード 3)。これは 5 回発生し、その間の間隔は 1、1、2、および 4 秒です。それから 4 秒後、アプレットはそれがしていたことを喜んで続けます。間隔の合計は最大 12 秒で、これがたまたま最も一般的な遅延の長さです。お客様のオフィスのネットワークがホストに到達できないと応答するかどうかはわかりません。Ping はあちらこちらでも、私のこのシミュレートされたセットアップでもタイムアウトします。
これは JVM のバグなのか設計上のエラーなのか疑問に思っています。通常、クラスをインスタンス化するとスレッドがしばらくフリーズするとは思いません。また、クラスローダー/JVM がプロキシを使用することも期待しています。通常、ホスト名の解決を数回試すことは意味があると思いますが、このコンテキストで行うのが正しいことかどうかはわかりません。
編集 3
コメンターは正しいです。ピンポンをしているのはOSです。によって生成されたネットワークトラフィックをキャプチャしてこれを試しping www.google.com
、同じ動作を得ました。