Pages

自己紹介

某IT技術情報誌に連載記事を持っていたテクニカルライター。またもや休刊してしまったよ。
Copyright © 2011-2016 Daregada. All rights reserved. Powered by Blogger.

WindowsでJDK 7とJDK 8を切り替える際の注意点

2015-03-13

WindowsでJDK 7とJDK 8を切り替えて開発する場合、環境変数JAVA_HOMEを変更するだけではうまくいかない(かもしれない)よ、という話。

Abstract

理由
Java SE 8以降のパブリックJREで、環境変数PATHの設定を自分で行なわない(行なえない)レベルのユーザーへの対策が変わったため
対策
パブリックJREのjava.exeが優先的に実行される状態を回避して、環境変数JAVA_HOMEの変更のみでJDKを切り替えられる状態に戻す
解説

理由

Windowsに複数バージョンのJava開発環境(JDK)をインストールするレベルのユーザーなら、環境変数JAVA_HOMEにJDKのインストール先フォルダーを設定し、環境変数PATHに%JAVA_HOME%\binを含める、という手法はおなじみのものだろう。これにより、JAVA_HOMEの内容を変更するだけで、複数バージョンのJDKを切り替えて使うことができる。

いっぽう、Java実行環境(JRE)を自動更新するだけ、というWindowsのエンドユーザーには、環境変数の設定を自分で行なえないレベルの人が、かなりの割合でいる(とOracleは想定している)ようだ。つまり、「Windowsをお使いの場合、環境変数PATHに、JREインストール先のbinフォルダーを追加してください」などとReadmeやWebページに書いても、単にそれを読み飛ばすか、書かれている意味や設定方法がわからないか、中途半端に設定するかして、サポートコストを増大させることになる。

そこで、Windows用のパブリックJREのインストーラー(JDKのインストール時にもデフォルトで実行される)は、エンドユーザーが何もしなくてもjava.exeをコマンド名のみで実行できるように、次のような「おせっかいな処理」を行なっている。

  • Java SE 7以前...%SystemRoot%(たいていはC:\Windows)以下のSystem32フォルダーに、パブリックJREのjava.exe, javaw.exe, javaws.exe(32ビット版JREのみ)をコピーする。通常、システム環境変数(*1)PATHにはこのフォルダーが最初から含まれるため、java.exeをコマンド名のみで実行できる
  • Java SE 8以降...%ProgramData%(たいていはC:\ProgramData)以下のOracle\Java\javapathフォルダーに、パブリックJREのjava.exe, javaw.exe, javaws.exeのシンボリックリンクを作成(あるいは更新)する。さらに、システム環境変数PATHにこのフォルダーが含まれていない場合は、先頭に追加する。その結果、java.exeをコマンド名のみで実行できる

*1 Windowsの環境変数は4種類(システム・ユーザー・ボラタイル・プロセス)ある。コントロールパネルで設定できるのは、OS全体で共有される「システム環境変数」と、ユーザーごとに独立した「ユーザー環境変数」の2つだ。

このように、Java SE 7以前とJava SE 8以降では、処理内容が大きく異なっている。このことを知らないでいると、Java SE 7以前で通用したJAVA_HOMEの変更によるJDKの切り替えがうまく動作しない。

また、これらの処理はパブリックJREをインストールするたびに行なわれるので、複数のJDKをインストールして切り替えるレベルのユーザーは、その影響を受けないようにしておくべきである。パブリックJREを更新するたびにPATHをいじるとか、いやでしょ?

対策

簡単な対策は、パブリックJREのjava.exeが優先的に実行される状態を回避して、環境変数JAVA_HOMEの変更のみでJDKを切り替えられる状態に戻すことだ。以下にその手順を示す。

なお、システム環境変数PATHの内容は非常に長くなることがあるため、小さな「システム変数の編集」ダイアログで編集するのは苦痛以外の何物でもない。PATHの内容をコピーして好みのエディターに貼り付け、エディターで編集作業を行なうことをお勧めする。編集作業が完了したら、エディターからダイアログにコピー&ペーストすればいい。

  1. システム環境変数PATHの先頭にある「javapathフォルダー」を末尾に移動する

    最初の対策は、Java SE 8以降のパブリックJREが作成する、javapathフォルダーのシンボリックリンクを実行しないようにするためのものだ。といっても、実体としてのjavapathフォルダーを削除する必要はなく、PATHの先頭にある「javapathフォルダー」を末尾に移動するだけでいい。たいていは、PATHの先頭からC:\ProgramData\Oracle\Java\javapath;を切り取り、末尾に貼り付けるだけで済む。

    なお、このフォルダーをPATHから削除すると、次に(Java SE 8以降の)パブリックJREをインストールするときに、またもや先頭に追加されてしまう。PATHのどこかにこのフォルダーが含まれていれば、インストーラーはPATHの内容を変更しないので、削除せずに優先度の低い末尾に残しておくこと。

  2. System32フォルダーのjava.exeなどを削除する

    次の対策は、Java SE 7以前のパブリックJREが作成する、System32フォルダーのコピーを実行しないようにするためのものだ。このフォルダーには、コマンドプロンプトなどの重要なコマンドが存在するため、PATHから取り除くわけにはいかないし、PATHにおける位置(優先度)も先頭に近いままにしておきたい。

    ところが、System32フォルダーにコピーされたjava.exeの動作が曲者で、自分自身に埋め込まれたバージョンとレジストリに記録された最新バージョンを比較し、両者が異なる場合には実行を中止する(下図参照)。このため、Java SE 8以降をインストールした後は、JAVA_HOMEなどの設定にかかわらず、System32フォルダーにコピーされたjava.exeを正常に動作させることができない

    >c:\windows\system32\java -version
    Error: Registry key 'Software\JavaSoft\Java Runtime Environment'\CurrentVersion'
    has value '1.8', but '1.7' is required.
    Error: could not find java.dll
    Error: Could not find Java SE Runtime Environment.
    

    解決方法としては、System32フォルダーにコピーされたjava.exeを手動で削除することだ(管理者権限が必要)。コピー元(C:\Program Files\Java以下など)にあるjava.exe(内容は同一)は、バージョンチェックを行なわずに動作するので、コピー先のファイルを消しても問題ない。また、Java SE 7のパブリックアップデートは2015年4月までなので、この先パブリックJREの更新によってコピーが新たに作られる、という事態もあまり起きないだろう。

    64ビット版WindowsにJava SE 7以前の64ビット版パブリックJREをインストールした場合には、System32フォルダーのjava.exeとjavaw.exeが削除対象だ。エクスプローラー(削除時に管理者権限が必要)を使うか、管理者権限で起動したコマンドプロンプトから削除しよう。また、Java SE 7以前の32ビット版パブリックJREをインストールした場合には、System32フォルダーからリダイレクトされるSysWOW64フォルダーに、java.exe, javaw.exe, javaws.exeがコピーされる。こちらも忘れずに削除しておこう。

  3. システム環境変数JAVA_HOMEを設定し、システム環境変数PATHの末尾より前に%JAVA_HOME%\binを含める

    上記の2つの対策により、Java SE 8以降のパブリックJREと、Java 7 SE以前のパブリックJREの影響を取り除いたので、以前と同様に環境変数JAVA_HOMEを利用したJDKの切り替えが可能になる。

    以前と同様ではわからんという人のために説明すると、環境変数JAVA_HOMEに、あなたが利用したいバージョンのJDKのインストール先フォルダー(たとえば、C:\Program Files\Java\jdk1.7.0_75など)を設定し、環境変数PATHに%JAVA_HOME%\bin;を含める、というものだ。

    ひとつめの対策により、システム環境変数PATHの末尾には、Java SE 8のパブリックJREが作成するjavapathフォルダーが含まれるので、それより前のどこかに%JAVA_HOME%\bin;を書く必要がある。システム環境変数の設定にはユーザー環境変数の値は利用できないため、PATHで参照されるJAVA_HOMEもシステム環境変数として作成する必要があることに注意されたい(*2)。

    *2 どうしてもJAVA_HOMEをユーザー環境変数として作成したいなら、ユーザー環境変数のPATH%JAVA_PATH%\bin;を含めればいい。ユーザー環境変数PATHの内容は、そのユーザーに限り、システム環境変数PATHの後ろに追加されて使われる。この場合、システム環境変数PATHに含まれるjavapathフォルダーが必ず優先されてしまうので、パブリックJREをインストールするたびに、javapathフォルダーのシンボリックリンクを削除する必要がある(管理者権限が必要)。

解説

  • シンボリックリンクの活用

    最初は、「環境変数JAVA_HOMEの変更にあわせて、シンボリックリンクのリンク先を切り替える」ことを考えたが、Windowsにはalternativesシステム(*1)のような仕組みがないため、mklinkコマンドを使って手動で(あるいはスクリプトを組んで)シンボリックリンクを張り替える必要がある。面倒なので今回はパスした。

    *1: alternativesシステムは、同一ソフトの複数バージョンや、類似する複数のソフトのコマンドの優先順位を、シンボリックリンクを使って柔軟に切り替える仕組みだ。Debianが発祥だが、現在ではDebian以外のLinuxディストリビューションでも広く使われている。日本語で読める文献としては、Vine Linuxのドキュメント「alternatives による標準コマンドの切り替え」が詳しい。

  • パブリックJREをインストールしない、という対策

    次に、「パブリックJREをインストールしない」ことを考えた。具体的には、JDKのインストール時に、パブリックJREをインストール対象から除外すればいい(JDKのインストーラーがパブリックJREのインストーラーを起動しなくなる)。

    たしかに、パブリックJREをインストールしなければ、シンボリックリンクは作られないし(そもそもjavapathフォルダー自体が作られない)、System32フォルダーにjava.exeなどがコピーされることもない。JREそのものは、JDKのインストール先のjreフォルダーにも存在するので問題はない。

    問題は、あなたが毎回「JDKのインストール時にJREを除外してインストールする」ことを覚えていられるかどうかだ。

  • ファイルコピーからシンボリックリンクに変えた理由

    シンボリックリンクは、ファイルサイズがコンパクトで、作成時の負荷もコピーするよりずっと低いので、今回のような用途には理想的だ。むしろ、今までなぜコピーしていたのかを考えた方がいいだろう。

    Java SE 7以前のパブリックJREでファイルの実体をコピーしていた理由は、Windows XPが2014年4月までサポート対象に入っていたからだろう。XPはFAT32ファイルシステムをシステムパーティションに選択できた最後の世代で、FAT32ではシンボリックリンクを作成できないのだ(NTFSのリパースポイント機能を利用するため)。

  • System32フォルダーからjavapathフォルダーに変えた理由

    これまで利用していたSystem32フォルダーではなく、%ProgramData%以下のjavapathフォルダーを使うことにした理由は、64ビット版Windowsの普及が進んだからだろう。

    というのも、64ビット版WindowsのSystem32フォルダーには、WOW64の機能のひとつ「ファイルシステムリダイレクタ」にまつわるわかりにくさが存在するからだ。

    これは、簡単に言うと、64ビット版Windowsで実行中の32ビットアプリに対して、SysWOW64フォルダーをSystem32フォルダーだと錯覚させる仕組みだ。64ビットアプリは64ビット版DLL、32ビットアプリは32ビット版DLLを読み込んで動作させるために、フォルダーの切り替えを行なっている。

0 件のコメント:

コメントを投稿