覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

truncate(ftruncate) はサイズを切り詰める(減らす)だけじゃないよ

Linuxのシステムコールの truncate, ftruncate は、対象のファイルのサイズを切り詰めるものです。

書式は以下の通りです。

#include <unistd.h>
#include <sys/types.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
  • 引数
    • pathname / fd・・・サイズを切り詰めるファイルのパス / サイズを切り詰めるファイルのファイルディスクリプター
    • length ・・・切り詰めるサイズ(byte)
  • 戻り値
    • 0 ・・・成功
    • -1 ・・・エラー発生 (この場合 errno が適切にセットされる)

各関数で、ファイルのパスを指定するのか、ファイルディスクリプター(ファイル記述子)を指定するのかの違いはありますが、
基本的にファイサイズを切り詰めるという動作は同じです。

で、私がいつの間にか勘違いしてただけなのですが、
これらシステムコールは、ファイルのサイズを減らす動きをさせることができると同時に、元のファイルのサイズより大きな lengthを指定してファイルを伸張することも可能です。
伸張された場合、サイズが増えた部分を読み出すとヌルバイト (0x00) のデータが返されます。
(サイズが増えた部分には、すぐには物理ディスクは割り当てられず、対象ファイルはholeを含んだファイル(Sparse File)になるのが普通です)。

伸長される(ファイルサイズが増える)というパターンが、私の頭からは欠落していました。


ファイルサイズが増えるパターンも含めて念のためにtruncate, ftruncate の動作確認をしてみます

truncate の動作確認

sample01.txt というファイルのサイズを 10 byteにします。(システムコール呼び出しとは別で対象のファイルは先に用意しておきます)

■サンプルプログラム

truncate_sample.c

#include <unistd.h>
#include <sys/types.h>

#include <stdio.h>

int main() {
    if (truncate("sample01.txt", 10) < 0) {
        perror("truncate error!");
        return 1;
    }

    return 0;
}

以下でコンパイルします

gcc truncate_sample.c -o truncate_sample


そして以下で動作確認してみます

■ファイルサイズが減るパターン

$ echo 12345678901234 > sample01.txt
$
$ ls -l sample01.txt 
-rw-r--r--  1 user  user  15 10 20 14:50 sample01.txt
$
$ hexdump sample01.txt 
0000000 31 32 33 34 35 36 37 38 39 30 31 32 33 34 0a   
000000f
$
$ ./truncate_sample 
$
$ ls -l sample01.txt 
-rw-r--r--  1 user  user  10 10 20 14:51 sample01.txt
$ 
$ hexdump sample01.txt 
0000000 31 32 33 34 35 36 37 38 39 30                  
000000a

サンプルプログラム実行後にサイズが10バイトに減っていることがわかります


■ファイルサイズが増えるパターン

$ echo 1234 > sample01.txt
$
$ ls -l sample01.txt 
-rw-r--r--  1 user  user  5 10 20 14:57 sample01.txt
$
$ hexdump sample01.txt 
0000000 31 32 33 34 0a                                 
0000005
$ ./truncate_sample 
$ ls -l sample01.txt 
-rw-r--r--  1 user  user  10 10 21 14:57 sample01.txt
$
$ hexdump sample01.txt 
0000000 31 32 33 34 0a 00 00 00 00 00                  
000000a

サンプルプログラム実行後にサイズが10バイトに増えて、増えた部分がヌルバイト(0x00)のデータになっていることがわかります。


ftruncate の動作確認

sample02.txt というファイルのサイズを 20byte にします。(システムコール呼び出しとは別で対象のファイルは先に用意しておきます)

サンプルプログラム

#include <unistd.h>
#include <sys/types.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>


int main() {

    int fd = open("sample02.txt", O_WRONLY);

    if (fd < 0) {
        perror("open error!");
        return 1;
    }

    if (ftruncate(fd, 20) < 0) {
        perror("ftruncate error!");
        close(fd);
        return 1;
    }

    close(fd);

    return 0;
}

以下でコンパイルします

 gcc ftruncate_sample.c -o ftruncate_sample


そして以下で動作確認してみます

■ファイルサイズが減るパターン

$ echo 1234567890123456789012345 > sample02.txt
$
$ ls -l sample02.txt 
-rw-r--r--  1 user  user  26 10 20 15:06 sample02.txt
$
$ hexdump sample02.txt 
0000000 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
0000010 37 38 39 30 31 32 33 34 35 0a                  
000001a
$
$ ./ftruncate_sample 
$
$ ls -l sample02.txt 
-rw-r--r--  1 user  user  20 10 20 15:06 sample02.txt
$
$ hexdump sample02.txt 
0000000 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
0000010 37 38 39 30                                    
0000014

サンプルプログラム実行後にサイズが20バイトに減っていることがわかります


■ファイルサイズが増えるパターン

$ echo 123456789 > sample02.txt
$
$ ls -l sample02.txt 
-rw-r--r--  1 user  user  10 10 20 15:08 sample02.txt
$
$ hexdump sample02.txt 
0000000 31 32 33 34 35 36 37 38 39 0a                  
000000a
$
$ ./ftruncate_sample 
$
$ ls -l sample02.txt 
-rw-r--r--  1 user  user  20 10 20 15:08 sample02.txt
$
$ hexdump sample02.txt 
0000000 31 32 33 34 35 36 37 38 39 0a 00 00 00 00 00 00
0000010 00 00 00 00                                    
0000014

サンプルプログラム実行後にサイズが20バイトに増えて、増えた部分がヌルバイト(0x00)のデータになっていることがわかります


まとめ

truncate, ftruncate はファイルサイズを減らすだけではなく、増やすこともできることがわかりました。
そして増えた場合は、ヌルバイトのデータが読み出されることがわかりました。

そうそう無いと思いますが、仮想ファイルシステムなんかを作る場合にもこの仕様は明確に把握しておく必要があります。



関連エントリ

Javaで外部プロセスを実行する

Java8以上の世の中だと思いますので、外部プロセスを実行する場合はProcessBuilderクラスを使いましょう。

今回は、外部プロセスが出力する標準出力や標準エラー出力の内容は無視して、終了コードだけを取得する例となっています。

Javaで?外部プロセスを実行する場合、よく出る話ですが以下の考慮が必要です。

外部プロセスは標準出力(や標準エラー)に書き込みたいしたいのに、受け側のProcessのInputStreamがいっぱいになってしまいます。
そのため、ストリームから読み出してやらないとバッファーが不足して、書き込み側(外部プロセス)がブロッキング(一時停止)してしまい、
そのプロセスは終了しないことになります。


実行用クラスは以下のような感じになるかと思います。

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public final class CommandExecutor {

    public static int execute(String cmd, List<String> params, long timeoutSec) {
        List<String> cmdAndParams = new LinkedList<>();
        cmdAndParams.add(cmd);
        cmdAndParams.addAll(params);

        return execute(cmdAndParams, timeoutSec);
    }

    public static int execute(String cmd, long timeoutSec) {
        return execute(Arrays.asList(cmd), timeoutSec);
    }

    private static int execute(List<String> cmdAndParams, long timeoutSec) {
        ProcessBuilder builder = new ProcessBuilder(cmdAndParams);
        builder.redirectErrorStream(true);  // 標準エラー出力の内容を標準出力にマージする

        Process process;
        try {
            process = builder.start();
        } catch (IOException e) {
            throw new CommandExecuteFailedException("Command launch failed. [cmd: " + cmdAndParams + "]", e);
        }

        int exitCode;
        try {
            // 標準出力をすべて読み込む
            new Thread(() -> {
                try (InputStream is = process.getInputStream()) {
                    while (is.read() >= 0); 
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }).start();

            boolean end = process.waitFor(timeoutSec, TimeUnit.SECONDS);
            if (end) {
                exitCode = process.exitValue();
            } else {
                throw new CommandExecuteFailedException("Command timeout. [CommandPath: " + cmdAndParams + "]");
            }

        } catch (InterruptedException e) {
            throw new CommandExecuteFailedException("Command interrupted. [CommandPath: " + cmdAndParams + "]", e);
        } finally {
            if (process.isAlive()) {
                process.destroy(); // プロセスを強制終了
            }
        }

        return exitCode;
    }

    private CommandExecutor() {
    }
}


独自の例外クラスも一応定義

public final class CommandExecuteFailedException extends RuntimeException {

    public CommandExecuteFailedException(String message) {
        super(message);
    }

    public CommandExecuteFailedException(String message, Throwable cause) {
        super(message, cause);
    }
}


実行例は以下の通りです


存在するコマンドの実行

java -version

■実行用コード

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        System.out.println("Command start [java -version]");
        int exitCode = CommandExecutor.execute("java", Arrays.asList("-version"), 3);

        System.out.printf("Command end. [exitCode: %d]\n", exitCode);
    }
}


■実行結果

Command start [java -version]
Command end. [exitCode: 0]


正常終了しています


存在しないオプションを指定して実行

java -unknown

■実行用コード

(上記と同様でmainで実行していますが、同様の箇所のコードをかなり省略しています)

System.out.println("Command start [java -unknown]");
int exitCode = CommandExecutor.execute("java", Arrays.asList("-unknown"), 3);

System.out.printf("Command end. [exitCode: %d]\n", exitCode);


■実行結果

Command start [java -unknown]
Command end. [exitCode: 1]


exitCode = 1 で異常終了しています


タイムアウトするコマンドを実行

ping 127.0.0.1 -c 100

■実行用コード

タイムアウト時間を3秒にして実行しています

System.out.println("Command start [ping 127.0.0.1 -c 100]");
int exitCode = CommandExecutor.execute("ping", Arrays.asList("127.0.0.1", "-c", "100"), 3);

System.out.printf("Command end. [exitCode: %d]\n", exitCode);


■実行結果

Command start [ping 127.0.0.1 -c 100]
Exception in thread "main" net.yyuki.cmd.CommandExecuteFailedException: Command timeout. [CommandPath: [ping, 127.0.0.1, -c, 100]]
    at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:51)
    at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:18)
    at net.yyuki.cmd.trial.Main3.main(Main3.java:10)


タイムアウトして、CommandExecuteFailedExceptionがスローされています


存在しないマンドを実行

unknown_cmd -h

■実行用コード

存在しないunknown_cmdというコマンドを実行しています

System.out.println("Command start [unknown_cmd -h]");
int exitCode = CommandExecutor.execute("unknown_cmd", Arrays.asList("-h"), 3);

System.out.printf("Command end. [exitCode: %d]\n", exitCode);


■実行結果

Command start [unknown_cmd -h]
Exception in thread "main" net.yyuki.cmd.CommandExecuteFailedException: Command launch failed. [cmd: [unknown_cmd, -h]]
    at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:33)
    at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:18)
    at net.yyuki.cmd.trial.Main5.main(Main5.java:10)
Caused by: java.io.IOException: Cannot run program "unknown_cmd": error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at net.yyuki.cmd.CommandExecutor.execute(CommandExecutor.java:31)
    ... 2 more
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 3 more


対象のコマンドが存在せず、CommandExecuteFailedExceptionがスローされています


まとめ

ProcessBuilderクラスを利用して外部プロセスを実行してみました

Gson で JSONICの簡単なアダプタを作る

JavaでもJSONを扱わなければならなくなった初期の頃に、JSONICというライブラリのお世話になった方も少なく無いのでは無いでしょうか?

今となっては、jacksonGson 等の他のライブラリを使うのが一般的になっています。


JSONICのページにも以下のように書かれていました。

2018/7/1 JSONIC は、リポジトリを GitHub に移動するとともに今後機能強化が行われることがないメンテナンスモードに移行します。
機能、パフォーマンス共に優れた jackson への移行をおすすめいたします。

なんだか時代を感じます。

というわけで、JSONICを利用しているコードは基本的に、jacksonGson を利用するプログラムに書き換えた方がいいでしょう。

が、そういったプログラム改修が面倒だという場合は、アダプタを作るのも一つのやり方かもしれません。

今回は、JSONICのライブラリを利用していたコードをそのままに、Gsonで処理させるようにしてみます。

log4jのクラスを利用しているコードをのままで、slf4j で処理を実行する log4j-over-slf4j みたいなイメージです。


と言っても、今回はものすごく単純なサンプルですので、JSONICの機能を使い倒していればいるほど動かないと思います。


今回JSON文字列から変換するオブジェクトは以下とします。

import java.util.Arrays;

public final class Person {

    private String name;

    private int age;

    private String[] hobby;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
}


JSONICの動きの確認

まず、JSONIC の動きの確認のためにpom.xmlにJSONICライブラリを追記します

<dependency>
        <groupId>net.arnx</groupId>
        <artifactId>jsonic</artifactId>
        <version>1.3.10</version>
</dependency>


実行のためのサンプルコードは以下の通りです。以下2つの処理を行っています

  • Pesonのインスタンスを生成して、JSON文字列化 (encode)
  • JSON文字列からPesonのインスタンス生成 (decode)

■サンプルコード

import net.arnx.jsonic.JSON;

public class JSONICTrial {

    public static void main(String[] args) {
        Person p = new Person();
        p.setName("taro");
        p.setAge(10);
        p.setHobby(new String[] {"Reading", "Travel"});

        // オブジェクトからJSON文字列へ変換
        String json = JSON.encode(p);
        System.out.printf("%s -> >>encode>> -> %s\n\n", p, json);


        // JSON文字列からオブジェクトへ変換
        Person p2 = JSON.decode(json, Person.class);
        System.out.printf("%s -> >>decode>> -> %s\n\n", json, p2);
    }
}


実行結果は以下の通りです。

■実行結果

Person{name='taro', age=10, hobby=[Reading, Travel]} -> >>encode>> -> {"age":10,"hobby":["Reading","Travel"],"name":"taro"}

{"age":10,"hobby":["Reading","Travel"],"name":"taro"} -> >>decode>> -> Person{name='taro', age=10, hobby=[Reading, Travel]}


JSONICのアダプタと動きの確認

JSONICを利用したコードをGson で処理させるために、pom.xmlにGsonの依存関係を追記します

<dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
</dependency>

その上で、pom.xmlからJSONICライブラリの記述を除外します


JSONICを利用したコードをGson で処理させるために以下のコードを記述します

package net.arnx.jsonic;

import com.google.gson.Gson;

public final class JSON {
    private static final Gson gson = new Gson();

    public static String encode(Object o) {
        return gson.toJson(o);
    }

    public static <T> T decode(String json, Class<T> type) {
        return gson.fromJson(json, type);
    }
}


この状態で元のJSONICTrialを実行すると以下のようになります。

■実行結果

Person{name='taro', age=10, hobby=[Reading, Travel]} -> >>encode>> -> {"name":"taro","age":10,"hobby":["Reading","Travel"]}

{"name":"taro","age":10,"hobby":["Reading","Travel"]} -> >>decode>> -> Person{name='taro', age=10, hobby=[Reading, Travel]}

JSON文字列の項目の並び順が微妙に違っていますが、基本的に同じ結果が得られています。


まとめ

今回は、JSONICの超基本的なAPIのみをアダプタとして作成しましたが、それにより一応元のプログラムは改修なしでGsonで処理させて動作することがわかりました。



関連リンク

Gson - JsonWriterでインデント形式を変えてファイル出力してみる

以前JSON形式のデータをJavaで扱うためのGsonライブラリについて使い方を紹介しました。

今回はGsonライブラリのGsonオブジェクトを使わずにJSONファイルへの出力を行ってみます。
その際にインデントの形式をいくつか変えて出力します。

今回出力するJSONファイルの内容は以下のようなものとします

{
 "files": [
  {
   "name": "data-1.txt",
   "size": 1024,
   "type": "file",
   "update.time": 1538835662719
  },
  {
   "name": "data-2.txt",
   "size": 2048,
   "type": "file",
   "update.time": 1538835662719
  },
 ・・・繰り返し
 ]
}


インデントなしで出力

サンプルコードは以下の通りです。JsonWriter.setIndent メソッドを実行していないのが着目点になります。


■サンプルコード

import com.google.gson.stream.JsonWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class GsonTrial1 {

    public static void main(String[] args) throws Exception {
        write1(new File("sample1.json"));
    }

    private static void write1(File file) throws IOException {
        try (JsonWriter writer = new JsonWriter(new FileWriter(file))) {

            writer.beginObject();
            writer.name("files");
            writer.beginArray();

            for (int i = 1; i <= 10; i++) {
                writer.beginObject();
                writer.name("name").value("data-" + i + ".txt");
                writer.name("size").value(1024 * i);
                writer.name("type").value("file");
                writer.name("update.time").value(System.currentTimeMillis() + i);
                writer.endObject();
            }
            writer.endArray();
            writer.endObject();
        }
    }
}


■実行結果(ファイルへの出力内容)

{"files":[{"name":"data-1.txt","size":1024,"type":"file","update.time":1538835662718},{"name":"data-2.txt","size":2048,"type":"file","update.time":1538835662718},{"name":"data-3.txt","size":3072,"type":"file","update.time":1538835662718},{"name":"data-4.txt","size":4096,"type":"file","update.time":1538835662718},{"name":"data-5.txt","size":5120,"type":"file","update.time":1538835662718},{"name":"data-6.txt","size":6144,"type":"file","update.time":1538835662718},{"name":"data-7.txt","size":7168,"type":"file","update.time":1538835662718},{"name":"data-8.txt","size":8192,"type":"file","update.time":1538835662719},{"name":"data-9.txt","size":9216,"type":"file","update.time":1538835662719},{"name":"data-10.txt","size":10240,"type":"file","update.time":1538835662719}]}

JSONファイルとしての必要な形式は当然満たしていますが、人間が読むにはちょっと辛いかもしれません。


インデントありで出力

上記のサンプルでは出力されるJSONファイルの内容が人間にとって読みにくいので、今度は読みやすいようにインデントして出力してみます。


サンプルコードは以下の通りです。先ほどのサンプルとは異なりJsonWriter.setIndent メソッドを実行してインデントするようにしています。


■サンプルコード

import com.google.gson.stream.JsonWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class GsonTrial2 {

    public static void main(String[] args) throws Exception {
        write2(new File("sample2.json"));
    }

    private static void write2(File file) throws IOException {
        try (JsonWriter writer = new JsonWriter(new FileWriter(file))) {
            writer.setIndent(" ");  // インデントするようにした

            writer.beginObject();
            writer.name("files");
            writer.beginArray();

            for (int i = 1; i <= 10; i++) {
                writer.beginObject();
                writer.name("name").value("data-" + i + ".txt");
                writer.name("size").value(1024 * i);
                writer.name("type").value("file");
                writer.name("update.time").value(System.currentTimeMillis() + i);
                writer.endObject();
            }
            writer.endArray();
            writer.endObject();
        }
    }
}


■実行結果(ファイルへの出力内容)

{
 "files": [
  {
   "name": "data-1.txt",
   "size": 1024,
   "type": "file",
   "update.time": 1538836735394
  },
  {
   "name": "data-2.txt",
   "size": 2048,
   "type": "file",
   "update.time": 1538836735395
  },
  {
   "name": "data-3.txt",
   "size": 3072,
   "type": "file",
   "update.time": 1538836735397
  },
  {
   "name": "data-4.txt",
   "size": 4096,
   "type": "file",
   "update.time": 1538836735398
  },
  {
   "name": "data-5.txt",
   "size": 5120,
   "type": "file",
   "update.time": 1538836735399
  },
  {
   "name": "data-6.txt",
   "size": 6144,
   "type": "file",
   "update.time": 1538836735400
  },
  {
   "name": "data-7.txt",
   "size": 7168,
   "type": "file",
   "update.time": 1538836735401
  },
  {
   "name": "data-8.txt",
   "size": 8192,
   "type": "file",
   "update.time": 1538836735402
  },
  {
   "name": "data-9.txt",
   "size": 9216,
   "type": "file",
   "update.time": 1538836735404
  },
  {
   "name": "data-10.txt",
   "size": 10240,
   "type": "file",
   "update.time": 1538836735405
  }
 ]
}

JSONとしての内容に違いはありませんが、最初のサンプルに比べて人間が読みやすい形式になりました。


インデントありで出力(少しファイルサイズを気にする)

先ほどの実行サンプルで出力されるJSONファイルの内容は人間の目には見やすくなったのですが、
スペースや改行が入りまくるので、List内のデータ量が増えると出力したファイルサイズかなり大きくなってしまいます。

ファイルサイズをある程度抑えつつ、人間の目にも見やすい感じで出力してみます。


サンプルコードは以下の通りです。先ほどのサンプルとは異なりJsonWriter.setIndent メソッドを繰り返し処理の中で複数回実行してインデントをONにしたりOFFにしたりしています。


■サンプルコード

import com.google.gson.stream.JsonWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class GsonTrial2 {

    public static void main(String[] args) throws Exception {
        write3(new File("sample3.json"));
    }

    private static void write3(File file) throws IOException {
        try (JsonWriter writer = new JsonWriter(new FileWriter(file))) {
            writer.setIndent(" ");  // インデントON

            writer.beginObject();
            writer.name("files");
            writer.beginArray();

            for (int i = 1; i < 10; i++) {
                writer.beginObject();
                writer.setIndent("");    // インデントOFF
                writer.name("name").value("data-" + i + ".txt");
                writer.name("size").value(1024 * i);
                writer.name("type").value("file");
                writer.name("update.time").value(System.currentTimeMillis() + i);
                writer.endObject();
                writer.setIndent(" ");   // インデントON
            }
            writer.endArray();
            writer.endObject();
        }
    }


■実行結果(ファイルへの出力内容)

{
 "files": [
  {"name":"data-1.txt","size":1024,"type":"file","update.time":1538837153533},
  {"name":"data-2.txt","size":2048,"type":"file","update.time":1538837153534},
  {"name":"data-3.txt","size":3072,"type":"file","update.time":1538837153535},
  {"name":"data-4.txt","size":4096,"type":"file","update.time":1538837153536},
  {"name":"data-5.txt","size":5120,"type":"file","update.time":1538837153537},
  {"name":"data-6.txt","size":6144,"type":"file","update.time":1538837153538},
  {"name":"data-7.txt","size":7168,"type":"file","update.time":1538837153540},
  {"name":"data-8.txt","size":8192,"type":"file","update.time":1538837153541},
  {"name":"data-9.txt","size":9216,"type":"file","update.time":1538837153542}
 ]
}

人間が読みやすい形式を保ちつつ、ファイルサイズをある程度抑えた出力になりました、


まとめ

GsonJsonWriter.setIndentをうまく利用することで、出力するJSONののインデントをある程度制御することができました。



関連エントリ

Java 11リリースに関するメモ

先日、Java 11が正式リリースされました。

ご存知の通りですが、本バージョンからOracle JDKが有償となりました。

その辺についての自分用のメモです。(不正確だったり間違っている内容もあるかもしれません)


概要

Java 11リリースに関連する内容は以下の通りです

  • Java 9から半年ごとにメジャーバージョンが上がるリリースがされていたが、(Oracle)のJava 11は長期サポート(LTS:Long Term Support)対象のバージョンになる
  • Oracleでは、LTS対象だったJava 8のサポートは2019年1月に終了する
  • 32bit版のバイナリは提供されない(?)
  • JavaFXがJDKから削除された(分離された)
  • Java Web Startが削除された
  • sun.misc.Unsafe(の一部?)が削除された


機能的なリリース内容などは以下のリリースノートを参照ください。

JDK 11 Release Notes


Oracle JDK

多くの人、多くのシステムで利用されていたのがOracleのJavaだと思います。

本バージョンから有償となりましたが、Oracleのサイトから無料でダウンロードできてしまいます。
が、ライセンスが変更になっています。
Oracle Java SE License

商用環境等で利用する場合、金を払う必要があります。
開発、テスト、プロトタイプ作成、デモを目的としていて業務、商用又は本番利用を目的として使用しない場合は無償利用して良いようです。

ダウンロードする場合は以下から実施ください。

Java SE Development Kit 11- - Downloads


OpenJDK

Javaの開発コミュニティで、Oracleがメインスポンサーとして開発が行われています。ほぼすべてのJavaの中心となります。

(もともとLTSの話が出ていましたが、結局)LTSに対応した3年間のバグフィックスやセキュリティパッチの提供予定が表明されていません。

ダウンロードは以下から実施ください。(Oracleがビルドしたバイナリを提供しています)

JDK 11 GA Release


AdoptOpenJDK

自分自身、最近まで存在すら知らなかったです。。。

OpenJDKのビルドを提供するコミュニティで、MicrosoftやIBMなどがメインスポンサーに名を連ねています。

無償で利用できますし、LTSに関して4年間のサポートを表明しています。

Support | AdoptOpenJDK - Open source, prebuilt OpenJDK binaries


他でもよく書かれていますが、無償利用したい場合はこのJDKが最有力候補かもしれません。


現時点(2018/9/29)では、リリースされていませんが、以下リンク先等からダウンロードできるようになると思われます。

Archive | AdoptOpenJDK - Open source, prebuilt OpenJDK binaries


他のJava

RedHat等の各種LinuxディストリビューションやAzul SystemsなどもOpenJDKのビルドバイナリを提供しています。


現場でよく聞く声

Java 11リリースに関して、Java(Oracle JDK)を使ってサービスを提供している側やそのシステムを利用しているユーザからよく聞く声として以下のようなものがあります。

  • これまでも、Javaに関しては別にOracleのサポートは受けたことがない。そのため、OpenJDKの利用でも問題ない。
    が、大きな企業の正式な後ろ盾が無いのが気になる。
  • 脆弱性の対応がなされない古いバージョンを使い続ける選択肢は基本的に考えていない。
  • 有償版のOracle JDKを利用してもいいが、その費用はサービス提供者が支払うのか?ユーザが払うのか?が課題となっている
  • 一部APIが削除されているので、Java 8以下で動作させていたアプリが、Java 11では動かないケースが結構ある。
  • JavaでGUIアプリ作る場合は結局何で作ればいいの?(JavaFXが分離されてしまった。Swingで作るのが良いの?)


まとめ

まとめというまとめは私にできませんが、Java 11はJavaにとっての大きな節目になるのかも?とは思います。



関連リンク