くたくたシェルのブログ

気ままに楽しむ燃え殻 (ash) のような人間が自分の頭の舵取りをするために書いています。つぶやくよりは考え、考え過ぎるよりは吐き出す、そんな記録を脳みその外に置きます。

Mac OS X 10.8.5 上での JDK バージョンアップ

これまで JDK 1.6 で作業していましたが、だいぶ取り残されつつあるので 1.7 や 1.8 をインストールすることに。やり方を一度調べたことがあるのですが、忘れてしまっていました。Java SE という表現と JDK という表現が混在しますがご愛嬌。

参考: OSXでJavaのバージョンを切り替える - Qiita

もともと私の頭の中が雑然としてきていたので、作業中に混乱したこともあり、整理もかねて書きとめておきます。

  1. Oracleのサイトから Java SE の 7 と 8 をダウンロード。
  2. パッケージの簡単操作でインストール。
  3. コマンドラインJDKのバージョンを指定できるよう ~/.bash_profile に追記。
  4. Eclipse で使う JRE (JDK) の設定

以下では 3 と 4 について書き留めます。

なお、最後の方では脱線して、GUIEclipse を起動する際の Java バージョンを切り替えることを試みていますが、できていません。試行錯誤の記録として残しておきます。 

コマンドラインで作業するための設定

特に何もしなければ、最新の(もしかしたら最後にインストールした)バージョンの JDK が使われるように切り替わります。このことをコマンドラインで確認します:

$ which java

/usr/bin/java
$ java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

$ echo $JAVA_HOME

なお、JAVA_HOME は未定義なので、必要な場合は定義する必要があります。

余談1

余談ですが、実は /usr/bin/javaシンボリックリンクです。しかも多段階:

$ ls -l /usr/bin/java
/usr/bin/java -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java

$ ls -l /System/Library/Frameworks/JavaVM.framework/Versions/Current
/System/Library/Frameworks/JavaVM.framework/Versions/Current -> A

$ /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java -version

java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

より古いバージョンの Mac OS X だと状況が違ったみたいですが、このディレクトリ A の配下が最新版で置き換えられているようです。このことは、例えば古いバージョンの JDK 1.7 を明示的に使うことはできないことを意味しており、次の結果からも何となく想像がつきます。ディレクトリ Versions/ 配下も覗いてみましょう。

$ ls -l /System/Library/Frameworks/JavaVM.framework/Versions
1.4 -> CurrentJDK
1.4.2 -> CurrentJDK
1.5 -> CurrentJDK
1.5.0 -> CurrentJDK
1.6 -> CurrentJDK
1.6.0 -> CurrentJDK
A
Current -> A
CurrentJDK -> /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents

一見バージョンアップするたびに更新されていくと思わしきシンボリックリンク群がありますが、よく見ると違うようです。"Current" 以外のシンボリックリンクはさらに別のシンボリックリンク "CurrentJDK" に集約されており、さらにその指す先に JDK 1.6 の実体があります。

ちなみに下線部のディレクトリを覗いてみると、1.6 以外は存在しないことが分かります:

$ ls /System/Library/Java/JavaVirtualMachines/
1.6.0.jdk

そこへのシンボリックリンクを Versions/ の流儀に沿って作ってやればバージョンの切り替えはしやすくなりそうですが、先ほどインストールした 1.7 や 1.8 がここに配置されていないのが気になり、もう少し調べてみました(余談にて後述)

さて、脱線してしまったので本来の作業に戻ります。

本題

コマンドラインJDK のバージョンを切り替えて使うには java_home コマンドが便利です。*1 環境変数 JAVA_HOME と PATH を書き換える設定を ~/.bash_profile に追記します:

#export JAVA_HOME=`/usr/libexec/java_home -v 1.6`

#export JAVA_HOME=`/usr/libexec/java_home -v 1.7`

export JAVA_HOME=`/usr/libexec/java_home -v 1.8`

 

export PATH=$JAVA_HOME/bin:$PATH

切り替え用のシェルスクリプトはあえて作りません。上記の export 文を適宜有効化/無効化して切り替えます。

余談2

なお、java_home コマンドが返す具体的なパスは次の通りです:

$ /usr/libexec/java_home -v 1.6
/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
$ /usr/libexec/java_home -v 1.7
/Library/Java/JavaVirtualMachines/jdk1.7.0_67.jdk/Contents/Home
$ /usr/libexec/java_home -v 1.8
/Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home 

$ /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home

JDK 1.6 だけベースディレクトリが違いますが、これは前述の余談にて触れたシンボリックリンク CurrentJDK の実体のサブディレクトリです。上記の下線部のディレクトリを覗いてみましょう。

$ ls -1 /Library/Java/JavaVirtualMachines/
jdk1.7.0_45.jdk
jdk1.7.0_67.jdk
jdk1.8.0_20.jdk

ここは先ほど覗いたのとは逆に、これまで使っていたはずの JDK 1.6 がありません。*2

EclipseJDK のバージョンごとに切り替えるための設定

本題

前述の余談2で確認した JAVA_HOME のパスを Eclipse にも「インストール済みのJRE」として登録します。*3 

余談3

ちなみに、java コマンド群の実体は複数あるので要注意です。幾重にもシンボリックリンクが介在しており、追いかけていると混乱します。*4

$ /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
$ /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/bin/java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

また、同名のコマンド同士はハードリンクになっている訳でもなく、またインストーラが同じファイルを複数の場所にコピーしているのかなと思いきや、違うようです。同じ java コマンドだけどファイルサイズが違ったり、異なる多くのコマンドのファイルサイズが同じだったりして、まだ理解が追いついていません:

$ cd /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands
$ ls -l java* | sed -e 's/  */ /g' | cut -d ' ' -f 5,9

54624 java
68224 java_home
54624 javac
54640 javadoc
54624 javah
54624 javap
49584 javaws

$ cd /Library/Java/JavaVirtualMachines/jdk1.8.0_20.jdk/Contents/Home/bin/

$ ls -l java* | sed -e 's/ */ /g' | cut -d ' ' -f 5,9
99296 java
99376 javac
99376 javadoc
2293 javafxpackager
99376 javah
99376 javap
2293 javapackager

本題:コンパイラの準拠レベルの設定

最後に、Eclipseの設定で、「Java」→「コンパイラー」からコンパイラ準拠レベルを所望のものに設定します。

余談4

Eclipse 本体の起動の際に使われる Java のバージョン切り替えについて実際に試して調べてみたところ、有効なのは、

  1. eclipse.ini ファイル内の -vm 引数で指定したパスの java
  2. java へのパスが通っている環境変数 PATH

の2種類で、優先順位も上記の通りです(JAVA_HOME は無関係)。*5

余談5:Mac OS X の .app についての疑問

GUI から .app をダブルクリックして Eclipse を起動するとき、起動する JVM を切り替えたい(例えば、1つ前のバージョンの Java 7 で起動したい)場合、どうしたらいいか、というのが発端です。結論を先に言うと成功していないので、あくまで試行錯誤の記録です。

Mac OS で .app から起動するのはコマンド open -a <ApplicationName> と同義と思っているのですが、この場合、シェル環境はどうなるのでしょう?

コマンド open を実行した場合はターミナルが親プロセスとなるはず。親プロセスの環境を引き継ぐはずなので、ターミナルのシェル環境を引き継ぐはずですね。これは理解できますし、そのような挙動になることは確認できました。

では、GUI で .app を起動する場合の親プロセスは実際のところ何なのか?そもそもシェル環境を介在するのか? *6 8/22削除→ ps aeuxの実行結果によると PID が 1 のプロセスは launchd らしく、また launchd 自体は複数プロセス起動しているようです。この辺を調べていると、詳しい情報を発見:

システムワイドなロケール設定 〜 Mac OS X (その2) - 彷徨えるフジワラ

GUI で起動するときはシェルが介在しないとのこと。ここに介入するには

  • /etc/launchd.conf (デフォルトでは存在しないので新規作成)を設定して OS 再起動する方法
  • launchctl コマンド(lanunchd とのインターフェース)を使って環境を変える方法

の2つがあるようです。後者を選択し、やってみました:

$ ldpath=`launchctl getenv PATH`

$ launchctl setenv PATH `/usr/libexec/java_home -v 1.7`/bin:$ldpath

$ launchctl getenv PATH
/Library/Java/JavaVirtualMachines/jdk1.7.0_67.jdk/Contents/Home/bin:/usr/bin:/bin:/usr/sbin:/sbin

$ launchctl setenv JAVA_HOME `/usr/libexec/java_home -v 1.7`
$ launchctl getenv JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.7.0_67.jdk/Contents/Home

上記の実行後、Eclipse.app のエイリアスから起動しようとしたのですが最新の 1.8 で起動しました。ということは、JAVA_HOME だけでなく PATH も読み込まれていない(変更が反映されない)ということ。GUIで直接アプリを指定して起動しているのだから、PATH を考慮する必要ないだろうといわれればそれまでですが、さて、どうやって切り替えよう・・・。ターミナルから切り替えできるから良しとしました。

8/22追記: ps の l オプションで親プロセスの ID が分かるようなので、ps alx で調べたところ、eclipse の親はユーザー権限で起動した launchd で、さらにその親が root で起動された launchd のようです。launchctl はどちらの launchd とのインターフェースを担うのか気になりますが、これ以上の調査は断念。

*7

余談6: ターミナルのタブとシェルの関係

ターミナルの新規タブを開くとログインシェルが起動します(.bash_profileの変更が反映される)が、このとき .bashrc は読み込まれないようです。*8

ターミナルのタブ内で bash コマンドを引数なしで実行してシェルを子プロセス(=対話シェル)として起動すると .bash_profile は読み込まれず .bashrc が読み込まれます(対話シェルの挙動)。

余談7

以上のことから、余談が多過ぎていること、すなわち調べものをしている最中に脱線し過ぎており、効率が悪いことがはっきりしました。*9

*1:これは JDK 同梱なのかな?

*2:ちなみに、1.7.0_45 は今回インストールしたものではありません。過去に JDK 1.7 をインストールしたこと、やり方を調べたことは何となく記憶にあるので、恐らくこれがそれでしょう。笑

*3:不思議なのですが、なぜ「インストール済みのJDK」でなく「インストール済みのJRE」なんだろう?Eclipse が内部的にコンパイラを持っているらしいという情報はあったのですが、ならば実は Java SE をインストールする必要はなく、それを使うという選択肢もあるのかな? 9/6追記:リンク先の情報によると、"EclipseJava 開発用のプラグインである JDT(Java Development Tools)の中に、「ECJ (Eclipse Compiler for Java)」という Eclipse 独自のコンパイラが含まれている" とのことです。インストール済みの JRE は開発環境を実行環境に揃えたい目的で使っていたのでした。ど忘れ・・・別件ですが、道理でJDKに同梱されているDerbyのjarが実行時に見つからない訳ですね。

*4:javaws に至っては指す先が他のコマンドと異なる javaws -> /System/Library/Java/Support/Deploy.bundle/Contents/Home/bin/javaws であり、唯一浮いているという謎も。調べてみるとこれは Java Web Start というものらしく・・・詳しくは調べず。

*5:引数 -vm の値はJAVA_HOME の値ではいけません。 eclipse.ini - Eclipsepedia によると、JAVA_HOME ディレクトリを指定するだけでは不十分で、実行可能な java を指定する必要があるようですね。抜粋するとこんな感じにするようにとのことです:

(略)

-vm
/Library/Java/JavaVirtualMachines/jdk1.7.0_67.jdk/Contents/Home/bin/java
-vmargs
(略)

もし仮に、例えば 1.8 の JAVA_HOME を指定し、PATH には 1.7 の java へのパスを通してみると、なんと 1.6 で起動します。この場合、eclipse.ini の -vm 引数を削除してやると PATH に通っている 1.7 の java で起動します。このあたりの挙動は謎です。

*6:8/22削除 → Finder が親だったりして?ps で見る限り Finder.app は確かに起動しています。でもどうしたらJavaのバージョンを切り替えれるか不明。init プロセスとかブートストラップの設定でなんとかできないかと思いきや、Mac OS にはそんなプロセスはなく Linux の概念だし、そもそも無理な発想だったという・・・。

*7:脱線しまくりですが、.app を直接でなくエイリアスを作って起動しようとしているのですが、エイリアスシンボリックリンクも似て非なるものですね。。Mac OS の深みにはまりそうな予感がして、一旦諦めました・・・。

*8:man bash によると、ログインシェルとして起動されたときには /etc/profile と ~/.bash_profile が読み込まれ、対話シェルとして起動されたときには ~/.bashrc が読み込まれます。ちなみに、/etc/bashrc は存在しますが読み込まれないようで、man bash でも触れられていません

*9:やりたいことが出来れば良いと割り切れればいいんでしょうけど、気になるんだから仕方がないですね・・・。