読者です 読者をやめる 読者になる 読者になる

#chiroito ’s blog

Java を中心とした趣味の技術について

Oracle 公式の Docker レジストリからイメージを pull

Oracle 公式の Docker レジストリには、Oracle WebLogic Server、Oracle JDK、Oracle Database、MySQL などの Oracle 製品がインストールされたイメージがあります。これらの公式イメージは Oracle 社のシングルサインオンアカウントを登録するだけで使用できます。

注:日本国内からはまだ利用できません。US、UK、オーストラリアに在住の方は利用できます。(2017年1月31日現在)

ブラウザで以下の URL から Oracle Container Registry へアクセスし、サインインします。アカウントを Docker リポジトリと紐付ける画面になりますので、紐付けましょう。紐付けが完了した後に再度アクセスすると用意されているイメージのリストが見られるようになります。
https://container-registry.oracle.com

シングルサインオンのアカウントである Oracle.comプロファイルを新規作成するには以下の URL から作成できます。
Oracle | Create Account
不明点がある場合はこちらを参考にして下さい。
Oracle.comプロファイルについて

紐付けが完了すると、次のように docker コマンドでリポジトリにログインして、イメージを pull できるようになります。

c:\>docker login container-registry.oracle.com
Username: <登録したメールアドレス>
Password: <登録したパスワード>
Login Succeeded

c:\>docker pull container-registry.oracle.com/java/serverjre:latest
latest: Pulling from java/serverjre
67d242720c3e: Pull complete
23eaae4f2427: Pull complete
Digest: sha256:2af18347c9e283e8279e58ea2c8af59268d12b8800f7b7b8c4ca356eb884503f
Status: Downloaded newer image for container-registry.oracle.com/java/serverjre:latest

c:\>docker images
REPOSITORY                                     TAG          IMAGE ID            CREATED             SIZE
container-registry.oracle.com/java/serverjre   latest       4455f4c1edff        6 days ago          382.2 MB

Oracle WebLogic Server や Oracle Database が簡単に使えるようになりますので、Java EE や Oracle Master などの学習にお役立て下さい。

IntelliJ IDEA で CDI をデバッグする時の落とし穴

IntelliJ IDEA (以下 IntelliJ) を使用して CDI 実装の1つである Weld を使用したアプリケーションを開発している人は多いかと思います。 テストフェーズでは、テストが上手くいかず、原因を特定するためにデバッグ実行をして動きをステップ毎に確認することも多いと思います。しかしながら Weld はブレークポイントの入れ方次第で動作が大きく変わる事に注意が必要です。

今回は CDI の実装として Weld の 1.1 系と 2.3 系で確認してます。

問題が分かる例として次のような RequestScoped な Bean である HelloBean とサーブレットである HelloServlet を処理するとします。HelloBean はクラスフィールドとして作成したオブジェクトの数を保持し、オブジェクトが生成されると 1 つ加算し、オブジェクトが破棄されると 1 つ減算します。
※今回は AP サーバのスレッドを 1 つにして処理するため同期処理は気にしていません。

@RequestScoped
public class HelloBean {
    private static int OBJECT_NUM = 0;

    @PostConstruct
    public void init() {
        OBJECT_NUM++
    }

    @PreDestroy
    public void destroy() {
        OBJECT_NUM ++;
    }

    public String hello() {
        return "Hello";
    }
}

HelloServlet は HelloBean をインジェクトします。doGet メソッドは HelloBean オブジェクトが null かどうか確認し、HelloBean オブジェクトの hello メソッドを実行し、その前後で作成された HelloBean の数を出力します。

@WebServlet(name = "Hello", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    @Inject
    private HelloBean bean;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

        res.getWriter().println("<html><body>");
        res.getWriter().println("HelloBean is " + ((this.bean == null) ? "" : "not") + " null<br/>");
        res.getWriter().println("HelloBean count is " + HelloBean.OBJECT_NUM  + "<br/>");
        res.getWriter().println(this.bean.hello() + "<br/>");
        res.getWriter().println("HelloBean count is " + HelloBean.OBJECT_NUM  + "<br/>");
        res.getWriter().println("</body></html>");
    }
}

この時、出力される結果はどうなるでしょうか?
HelloBean クラスのオブジェクトがインジェクトされているため、bean フィールドは null にはならず、OBJECT_NUM の数は 0 から 1 に増加していると考えるかと思います。
実際に出力されるコードは以下の通り、1 回目の OBJECT_NUM は 0 になります。

HelloBean is not null
HelloBean count is 0
Hello
HelloBean count is 1

この理由は、@Inject で渡されるオブジェクトが HelloBean クラスのプロキシであり、HelloBean オブジェクトではないためです。プロキシ先となる HelloBean のオブジェクトは HelloBean が持っている特定のメソッドが初めて呼ばれたタイミングでが作られます。特定のメソッドは HelloBean クラスが持つメソッドと toString() メソッドです。
IntelliJ では、デバッグをしながら実行すると、以下のようにコードの右側にオブジェクトの toString() メソッドの結果が出力されます。

f:id:chiroito:20170131010326p:plain

そのため、IntelliJ で Weld をデバッグしながら実行すると toString() メソッドが呼ばれたタイミングでオブジェクトが生成されるため、デバッグをしていない時と動きが変わってしまいます。

HelloBean is not null
HelloBean count is 1
Hello
HelloBean count is 1

今回のサンプルではブレークポイントが 1 つ下の行だった場合には動きに差はありません。ですが、実際のアプリケーションではどこに問題があるかを特定するのはすごく難しいでしょう。

Java がファイルを読んでいるか strace を使って確認する

Java アプリケーションの動きを分析をしていると、javaプロセスがファイルを開いたのかどうか確認したいことがあります。自分で作成したアプリケーションがファイルを読み書きしていればログを入れて確認できますが、アプリケーションの変更が簡単に行なえない場合やファイルを読み書きしている部分がミドルウェアやライブラリの内部で行われているとファイルの読み書きを確認することは難しいです。

java プロセスは OS から見るとただのプロセスなので、ファイルの読み書きにはシステムコールを使用します。このシステムコールを監視することでアプリケーションの変更や停止をせずに java プロセスがファイルを読み書きしているかを確認できます。Linux では strace を使用してシステムコールを監視します。

次のサンプルプログラムは、第一引数に与えた引数をファイルパスとしてファイルを読み込みモードで開くアプリケーションです。

import java.io.*;
import java.nio.file.*;

public class FileOpen {
    public static void main(String[] args) {
        try(InputStream inputStream = Files.newInputStream(Paths.get(args[0]))){
            // なにもしない
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

今回は、このサンプルプログラムのソースファイル(FileOpen.java)を読込んで動作を監視します。監視されるコマンドはjava FileOpen FileOpen.javaになります。

このアプリケーションは try-catch-resource を使用して InputStream のオブジェクトを取得しますが、try句の中では使用しません。「Javaは勝手に最適化するって聞くし、結果を使用しない処理なのでファイルの読み込みはされないのでは?」と思う方もいるかもしれません。このアプリケーションはファイルの読み込むのかシステムコールを監視してみましょう。

システムコールの監視には strace コマンドを使用します。スレッド毎(-ff)に監視結果をstrace_java という接頭辞のファイルへ出力し(-o strace_java)、ファイルディスクリプタ番号とファイルパスの対応付けて(-y)プロセス(java FileOpen FileOpen.java)を実行してシステムコールを監視します。 実行が終わった後には strace_java から始まるファイルがスレッド分作成されます。ファイルはアプリケーションが使用するスレッドだけでは無く、JVM が使用するスレッドの分まで作成されます。

> strace -ff -y -o strace_java java FileOpen FileOpen.java
> ls -w1 strace_java*
strace_java.11802
strace_java.11804
strace_java.11805
strace_java.11806
strace_java.11807
strace_java.11811
strace_java.11812
strace_java.11813
strace_java.11814
strace_java.11815
strace_java.11816
strace_java.11817

※この方法は、java プロセスの実行から終了までを監視しますが、既に起動している java プロセスを監視する場合にはコマンドの代わりにプロセス ID を-p <PID>のように指定します。

これらの結果の中から、grep コマンドを使って FileOpen.java が関わるログを探し出します。

> grep FileOpen.java strace_java*
strace_java.11802:execve("/usr/bin/java", ["java", "FileOpen", "FileOpen.java"], [/* 24 vars */]) = 0
strace_java.11804:open("FileOpen.java", O_RDONLY)         = 4
strace_java.11804:close(4</home/oracle/FileOpen.java>)    = 0

このログを確認すると、アプリケーションは読み込み専用でファイルを開いて(open("FileOpen.java", O_RDONLY))、その後クローズ(close(4</home/oracle/FileOpen.java>))しています。strace を使うことで、アプリケーションの修正無しに確認できました。

この例では、アプリケーションの実行と監視を同時に行いましたが、既に動いているプロセスに対してはstrace -ff -y -o strace_java -P <PID>で監視できます。