CodeZine(コードジン)

特集ページ一覧

Java 12~15でアップデートされたJVMの動作に関する変更点は? マイクロサービスの普及による軽量化ニーズが背景に

Java 12からJava 15までの変更点を確認しよう 第2回

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2020/10/12 11:00

 前回はJava14・15で変わるJavaの言語仕様を中心に紹介しましたが、今回はJDKに関連するツールやJavaコマンドの実行時に利用するような、JVMの動作に関する変更点についても紹介します。

目次

Java12からJava15までのJavaVMへの主な変更点

 開発者や実行するアプリケーションを最適化するため、JavaVMに関する追加・変更もありました。特に最近では、マイクロサービスなどを実現するために、フレームワークもより軽量でかつ、配布しやすい形が望まれてきています。そして、サーバレスアーキテクチャという需要も高まりつつあるなかで、それらの影響がJavaの世界にもおよびつつあります。

 これらの変更はこれまでとJavaの適用範囲が変わってきたことも想定していると思われ、開発者だけではなく、Javaアプリケーションを運用する方も知っておくとよい追加・変更点を紹介します。

表1:Java12〜15でのJavaVMに関する追加・変更点
JEP 名称 リリースされたバージョン
JEP358 Helpful NullPointerExceptions Java14
JEP371 Hidden Classes Java15
JEP372 Remove the Nashorn JavaScript Engine Java15
JEP341 Default CDS Archives Java12
JEP350 Dynamic CDS Archives Java13
JEP343 Packaging Tool Java14(Incubator)
JEP372 Remove the Nashorn JavaScript Engine Java15

Helpful NullPointerExceptions - NullPointerExceptionの理由がわかりやすくなった

 まず、Java開発者やJavaのアプリケーション運用者によい影響である、ちょっとした例外メッセージに関する変更点を紹介します。

 Javaのアプリケーションでよく見かけるが、原因がよくわからない例外の1つにNullPointerExceptionがあります。プログラム時に考慮されていれば、もともと生じないはずのエラーであるゆえ、実際に生じるとなかなか原因がわかりません。特に、最近ではよりシンプルに記述しやすいスタイルが好まれるようになり、1行で多くの処理をしているコードをよく見かけるようになりました。しかし、1行で記述しているがゆえに、その行でNullPointerExceptionが発生すると具体的に何がNULLなのかわからないということがありました。今回の改善ではエラーメッセージが改善されているので、実際のエラー原因がよりわかりやすくなっています。

 例えば、リスト1のようなコードを使って、実際にNullPointerExceptionを発生させたとします。

[リスト1]設定する値をコンストラクタでチェックする場合の例(java15/src/main/java/jp/enbind/jdk15/Jep358.javaからの抜粋)
  public void case1(){
    Point p = new Point();
    int xx = p.child.child.x;   // ここでNullPointerExceptionを発生される
  }

  public static class Point {
    private int x;
    private int y;
    private Point child;
  }

 Java11などこれまでは、リスト2のようにエラーが発生している行数まではわかっても、実際にどのオブジェクトがないためにエラーが発生しているのかがわかりませんでした。そのため、このように1行でオブジェクトの参照をたどるようなコードはJava開発者が好んでも、Javaアプリケーション運用者からは好まれない傾向がありました。

[リスト2]Java11でのNullPointerExceptionのエラーメッセージ例
Exception in thread "main" java.lang.NullPointerException
    at jp.enbind.jdk15.Jep358.case1(Jep358.java:19)
    at jp.enbind.jdk15.Jep358.main(Jep358.java:14)

 今回の改善では、リスト3のように表示されるので、実際にどこでエラーが発生しているかがわかります。

[リスト3]Java15でNullPointerExceptionのエラーメッセージ例
Exception in thread "main" java.lang.NullPointerException: Cannot read field "child" because "p.child" is null
    at jp.enbind.jdk15.Jep358.case1(Jep358.java:19)
    at jp.enbind.jdk15.Jep358.main(Jep358.java:14)

 ちょっとしたメッセージの変更ですが、こういったエラーメッセージの変更は多くの方にとってメリットがあります。

Hidden class - 一般開発者からは見えないクラス定義が可能

 通常のプログラミングをしていると意識しない部分ですが、Java15からは通常の開発者からは使用できない隠しクラスが作れる機能が導入されます。例えば、Javaを使ったミドルウェアやフレームワークなどの開発を行っている方は、Javaのバイトコードを実行時に書き換えたり、作成したりする場合があります。

 これらのクラスは、意図的に一般の開発者からは見えないようにしたいときもありますが、ClassLoaderなどにアクセスすると見えてしまう場合があります。これまでも、sun.misc.Unsafe.defineAnonymousClassを使うことで同様のことができましたが、このようなJDKに依存する内部APIを利用することは非推奨であり、将来的には削除されます。

 そのため、Java15からは、java.lang.invoke.MethodHandles.LookupクラスにdefineHiddenClassメソッドが追加され、そちらでHiddenクラスが作成可能になります。例えば、簡単なHiddenクラスを作成するコードがリスト4です。

[リスト4]Hiddenクラスを作成する例(java15/src/main/java/jp/enbind/jdk15/Jep371.javaからの抜粋)
public void case1() {
    try {
        //(1) クラスのバイトコードを取得
        byte classBytes[] = toBytes(Sample.class);

        //(2)Hiddenクラスとして登録する
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        Class clz = lookup.defineHiddenClass(classBytes,true).lookupClass();

        //(3)登録したHiddenクラスからインスタンスを作成する
        Object obj = clz.getDeclaredConstructor(null).newInstance();

        ISample sample = (ISample)obj;
        sample.invoke();

        //(4)java.lang.ClassNotFoundException が発生
        Class clz2 = this.getClass().getClassLoader().loadClass(clz.getName());
    }
    catch(Exception ex){
      logger.log(System.Logger.Level.INFO,"error {0} - {1}",ex.getClass(),ex.getMessage());
      ex.printStackTrace();
    }
}

public byte[] toBytes(Class clz) throws IOException{
    //  (省略)
}

public interface ISample{
    void invoke();
}

public static class Sample implements ISample{
   @Override
   public void invoke() {
       System.out.println("invoked");
   }
}

 (1)はクラスのバイトコードを取得するコードです。実際には、Javassistのようなライブラリを使ってバイトコードの書き換えやその他、一般の開発者には見せたくないバイトコードのデータを用意します。

 そして、(2)でMethodHandles.Lookupを取得し、そのdefineHiddenClassメソッドを使いHiddenクラスとして登録します。実際に登録したクラスを利用する場合には、(3)のように登録した際に取得可能なClassオブジェクトを利用します。登録したクラスは(4)のようにClassLoaderからClassオブジェクトを取得しようとしても見つけることはできません。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。個人紹介主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしど...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

バックナンバー

連載:Java 12からJava 15までの変更点を確認しよう
All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5