KODAMA LAB
1998 — Bytecode, not a rewrite

1998年の .class
そのままブラウザで動かす

このページは、書き直した JavaScript を動かしているのではありません。 1998年に javac が吐いた .class ファイルそのものを読み込み、 JavaScript で書いた JVM がバイトコードを1命令ずつ解釈して実行しています。 Threadsynchronizedwaitnotify も、 本物と同じ意味で動いています。

7アプレット
45class 形式(Java 1.1)
80実装した命令
38実装した JDK API
こちらとは別に、JavaScript で書き直した版もあります 同じ 7本を JavaScript で書き直したものが ../js/ にあります。 あちらは「同じ絵を出す別のプログラム」、こちらは「当時のバイトコードそのもの」です。
見る →
HOW

しくみ

A JVM in JavaScript

部品は4つだけです。全部このディレクトリにあります。

classfile.js .class を読む。先頭の cafebabe から定数プール・フィールド・メソッド・ Code 属性・例外表まで、JVM 仕様 4章のとおりに解く。 1998年のものは major = 45(Java 1.1)
interp.js バイトコードを 1命令ずつ実行する。long は BigInt、 double は数値、longdouble はスタックと局所変数を 2枠使う、 というところまで仕様どおりに合わせてある。
(同)スレッド JavaScript には実行の流れが1本しかないので、Java のスレッドはこちらで用意する。 スレッドごとにフレームを持ち、400命令ずつ順ぐりに実行する。 Thread.sleep は起きる時刻を持って眠り、synchronized はモニタが取れなければ待ち、 wait() はモニタを手放して待ち集合に入る。 つまり Semaphore の待ち合わせは、本当に待ち合わせている。
rt.js アプレットが呼ぶ JDK。逆アセンブルして数え上げたら 38個だったので、それだけ用意した。 java.awt.Graphics は canvas の 2D コンテキスト、 java.awt.Image は画面外の canvas(だからダブルバッファが本当に効く)、 ButtonScrollbar は本物の DOM 部品。

背景が灰色なのは飾りではありません。AWT の Panel の既定の背景色(#C0C0C0)で、 当時のスクリーンショット(rot6sync.jpg など)と同じ色です。

SOURCE

どこまでが当時のものか

What is original, what is not

正直に分けて書いておきます。公開してあった .classアプレット本体の7個だけで、 それが呼んでいる FreeSoRVBB などのバイトコードは、そもそも公開されていませんでした。

1998年の .class
そのまま
SampleSample05Sample1Sample15Sample3Sample46個。 いずれも major = 45、1998年のバイト列に手を触れていません。
1998年の .java
から compile
FreeSoRWireFrameTRVBBVBBBPointerProducerConsumerPProducerCConsumerソースは当時のままで、javac --release 8 に通しただけです。
失われていたもの Sample2.class は壊れています。中身が全部 NUL(0x00)で、サーバ上の実体も同じでした。 Sample2.java は無事だったので、そこから compile し直しています。
壊れていた画像 紹介ページ(英語)の親指画像のうち rot1obj.jpgrot1bobj.jpg も 中身が全部 NUL でした。この JVM で 1998年のバイトコードを動かした画面から作り直してあります。 残り 5枚(rot6objrot6bobjrot6syncbbuf0bbuf1)は当時のままです。
復元したもの Semaphore.javaTR2.java は、ソースごと失われています。 Sample2Sample3Sample4 からの呼ばれ方だけを手がかりに書き直しました (new Semaphore(int)Wait()Signal()Value()new TR2(Dimension,int,Semaphore,Semaphore)DrawValuesync)。 Semaphore.javaTR2.java で中身を確かめられます。

なお Java アプレット自体が、もうどのブラウザでも動きません(2017年以降)。 java.applet パッケージは JDK 24 で JDK からも取り除かれました。 ここで java.applet.Appletjava.awt.Graphics として動いているのは、 rt.js が用意した中身です。

DEMO 01

一つのオブジェクトの回転

One rotating object

簡単なクラスを用いて 3D オブジェクトを生成する。断面の10点を Y 軸まわりに 36°ずつ回して回転体を作り、X・Y・Z の各軸まわりに 12°ずつ回し続ける。

Thread を1本立て、80 ミリ秒ごとに Rotate()repaint() を呼ぶ。 repaint() は上書きせずに既定の update() に落ちるので、毎回背景で消してから描き直す(だからちらつく)。

DEMO 02

一つのオブジェクトの回転(ダブルバッファ)

One object, double buffered

次は、ダブルバッファを用いて処理をする。createImage() で画面外に絵を作り、 できあがってから 1枚だけ貼る。update() を上書きして「消してから描く」のをやめている。

ここでの画面外バッファは、本当に別の canvas です。 Image.getGraphics() がその canvas の 2D コンテキストを返し、drawImage で貼っています。

DEMO 03

複数のオブジェクトの回転

Six objects in parallel

これら 3D オブジェクトは、並行に処理される。ここでは、六個のオブジェクトを生成する。

下に出ている「スレッド 7本」は飾りではありません。 TR が 6本(各自 40 ミリ秒ごとに自分を回す)と、80 ミリ秒ごとに repaint() する 1本です。 6本はそれぞれ勝手に回るので、そろいません。

DEMO 04

複数のオブジェクトの回転(ダブルバッファ)

Six objects, double buffered

次は、ダブルバッファを用いて処理をする。

絵が落ち着いて見えます。6本が勝手に回っていること自体は、ひとつ上と変わりません。

DEMO 05

同期 v.s. 非同期

Synchronous vs asynchronous

セマフォを使って、同期・非同期を切替える。絵の中の「Sync」ボタンを押してみてください。

6個のセマフォを輪にして、0番だけ初期値を 1 にしてあります。 各オブジェクトは「自分のセマフォを Wait() → 回す → 隣を Signal()」なので、 同期のときは札が輪を1周ずつ回り、1個ずつ順番にしか回りません。 数字は、そのセマフォの現在値です。
ボタンを押すと action(Event,Object)(AWT 1.0 の古い受け取り方)が呼ばれ、 待ち合わせをやめて 6個が勝手に回り出します。 この待ち合わせは synchronizedwait()notify() で本当に起きています。

DEMO 06

有限バッファ

Bounded buffers

有限バッファ問題を表現する。10枠のバッファに、作り手と使い手が入れたり出したりする。 上下のつまみで、作る速さと使う速さを変えられます。

上の矢印が入れる位置、下の矢印が出す位置。矢印の中の数字はセマフォの値で、 上が「あと何枠空いているか」(初期値 10)、下が「いくつ溜まっているか」(初期値 0)です。
作る方を速くすると上の数字が 0 になり、作り手が Wait() で止まります。 使う方を速くすると下の数字が 0 になり、使い手が止まります。 止まっているのは絵の演出ではなく、そのスレッドが本当にモニタの待ち集合に入っている状態です。

同じものを、数字ではなく●で表したもの。

中身の描き方が drawString から fillOval に変わっただけで、 待ち合わせの仕掛けは同じです。

FILES

この JVM のソース

Read the JVM itself

紹介ページ: index-j.html / 当時のページ: index-j-1998.html / JavaScript で書き直した版: ../js/