[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

情報処理IIIA(Javaプログラミング入門)第13回

目次 索引
13.1 より高度な話題
13.1.1 バイトコードと仮想マシン
13.1.2 アプレット・セキュリティ
13.1.3 ガベージ・コレクション
13.2 試験について
13.3 成績評価について
13.4 演習13
13.5 レポート課題
Java仮想マシン  Javaバイトコード  インタプリタ  オブジェクトコード  ガベージ・コレクション  機械語  原始プログラム  高級言語  コンパイラ  ソースコード  ヒープ  目的プログラム 

13.1 より高度な話題

13.1.1 バイトコードと仮想マシン

これまで、色々なJavaプログラムを作成し、実行してきました。 実は、コンピュータはJavaプログラムを理解しません。 コンピュータが直接Javaプログラムを実行しているわけではないのです。 ここでは、Javaプログラムが実行される仕組みについて説明します。

まず、コンピュータが直接理解できるのは、機械語だけです。 機械語machine language ) とは、コンピュータを直接制御するプログラミング言語です。 Javaプログラムが構造を持った英単語などの列なのに対して、機械語のプログラムは構造のないビットの列です。 また、同じ制御を行う場合でも、コンピュータ・システムが異なりますと、異なるビット列になります。 機械語によるプログラミングは、システムごとに異なるビット列を、構造不明のまま書く、困難な作業なのです。

プログラミングをもう少し楽にするために、高級言語が考え出されました。 高級言語high level language ) とは、コンピュータにやってほしいことを、人間にとって分かりやすく書けるように作られたプログラミング言語です。 高級言語はプログラムの構造を反映する文法を持ち、英単語など覚えやすい構成要素から成り立ちます。 Javaは高級言語の一つです。 Javaの他にも、CやBASICなど数多くの高級言語が使われています。 もちろん、高級言語で書いたプログラムをコンピュータに与えても、機械語でないので実行できません。

実行するために、コンパイラやインタプリタを利用します。 コンパイラcompiler ) とは、高級言語で書かれたプログラムを機械語のプログラムに変換するソフトウェアです。 高級言語の中では、例えばC言語が通常コンパイラを使います。 C言語でプログラムを書き、Cコンパイラによってそれを機械語に変換したあとで、コンピュータにその機械語プログラムを実行させるのです。 コンピュータ・システムが異なれば機械語も異なりますので、システムごとにCコンパイラを用意する必要がありますが、Cプログラム自身はシステムの差をあまり意識せずに書けるのです。 なお、変換前のプログラムは ソースコードsource code ) 、または 原始プログラムsource program ) とよばれます。 また、変換後のプログラムは オブジェクトコードobject code ) 、または 目的プログラムtarget program ) とよばれます。

一方、 インタプリタinterpreter ) とは、高級言語で書かれたプログラムを少しずつ読みながら、その部分に対応する機械語プログラムを探して実行していくソフトウェアです。 高級言語の中では、例えばBASICが通常インタプリタを使います。 BASICでプログラムを書き、BASICインタプリタを実行しますと、プログラムの内容を少し読んでは機械語プログラムを見つけて実行ということを繰り返します。 コンパイラの場合と同様に、システムごとにBASICインタプリタを用意する必要がありますが、BASICプログラム自身はシステムの差をあまり意識しなくてもよいのです。

さて、Javaプログラムは、コンパイラ・インタプリタ方式で実行されます。 コンパイラ・インタプリタ方式とは、さまざまなコンピュータ・システムの共通語のような機械語を定め、コンパイラによって原始プログラムを共通機械語のプログラムに変換し、その後で共通機械語インタプリタを実行するという方式です。 共通機械語は、実在する必要のないコンピュータ・システムの機械語と考えられます。 そこで、仮想マシン(または仮想計算機)という概念を導入し、共通機械語は仮想マシンの機械語であると位置付けることにします。 Javaのための共通機械語は、 JavaバイトコードJava Bytecode ) です。 Javaのための仮想マシンは、 Java仮想マシンJava virtual machine ) とよばれます。

ここで、Javaアプリケーションを実行するときに、javacコマンドとjavaコマンドを用いたことを思い出してください。 javacコマンドは、Javaプログラム(〜.java)をバイトコード(〜.class)に変換するコンパイラです。 そして、javaコマンドはバイトコード・インタプリタなのです。 一方、Javaアプレットを実行するときは、javacコマンドでバイトコードを生成し、それをアプレット・ビューアやWWWブラウザに読み込ませました。 アプレット・ビューアやWWWブラウザには、バイトコード・インタプリタの機能が備わっていて、それが働いているのです。 アプレット・ビューアやWWWブラウザの中には仮想マシンがあると言うこともできます。

13.1.2 アプレット・セキュリティ

この授業では、アプレットを使ってお絵描きをしました。 本当は、アプレットはもっと色々なことができます。 楽しいアプレットが埋め込まれたホームページは非常に魅力的です。

ここで、アプレットもれっきとしたプログラムであること、そしてそのホームページを訪れると、アプレットが転送され、あなたのコンピュータの上で動くことを思い出してください。 悪意のある人の作ったアプレットが、あなたのコンピュータのファイルを消したり、個人情報を盗んだりしないのでしょうか。

この心配への答えは、「アプレットにできることは制限されていて、悪事を働くのは困難である。」です。 以下では、個人情報(具体的にはユーザ名)がアプレットで取り出せないことを確かめます。

はじめに、Javaアプリケーションで、Javaシステムに関する情報(システム・プロパティとよばれます)を取り出します。 次のプログラムは、Javaシステムが動いているOSの名前を表示します。

/*  1*/ class PropertyTest1 {
/*  2*/     public static void main (String[] args) {
/*  3*/         String s = System.getProperty("os.name");
/*  4*/         System.out.println(s);
/*  5*/     }
/*  6*/ }
b00a001@AsiaA1:~/java% java PropertyTest1
Mac OS X
b00a001@AsiaA1:~/java%

3行目の System クラスのクラスメソッド getProperty がシステム・プロパティを取り出します。 引数はシステム・プロパティの名前(文字列)です。 OSの名前はos.nameで、ユーザ名はuser.nameです。 (システム・プロパティは他にもあります。) したがって、3行目の "os.name""user.name" に置き換えますと、ユーザ名(この場合はb00a001)が表示されます。

次に、同じことをアプレットで行ないます。 7行目の Graphics クラスのインスタンスメソッド drawString はアプレットで文字列を描画するものです。 この第2引数と第3引数は、文字列イメージの左下の座標です。

PropertyTest2.java
/*  1*/ import java.applet.*;
/*  2*/ import java.awt.*;
/*  3*/
/*  4*/ public class PropertyTest2 extends Applet {
/*  5*/     public void paint (Graphics g) {
/*  6*/         String s = System.getProperty("os.name");
/*  7*/         g.drawString(s, 0, 20);
/*  8*/     }
/*  9*/ }
PropertyTest2.html
<applet code="PropertyTest2.class" width="200" height="100">
</applet>
b00a001@AsiaA1:~/java% appletviewer PropertyTest2.html &
b00a001@AsiaA1:~/java%
アプレットビューア
Mac OS X
アプレットが開始されました。
図 13.1  OSの名前を描画するアプレット

さて、 "os.name""user.name" に置き換えて、Javaアプリケーションのときのようにユーザ名が取り出せるでしょうか。 試してみますと、アプレット・ビューアは起動しますが、ユーザ名は描画されません。 その代わり、「ターミナル」にエラーメッセージが出力されます。 セキュリティ上の問題があるのでアプレットを停止するということです。

b00a001@AsiaA1:~/java% appletviewer PropertyTest2.html &
java.security.AccessControlException: access denied
(java.util.PropertyPermission user.name read)
    (中略)
b00a001@AsiaA1:~/java%

注意: 設定によっては、アプレット・ビューアでユーザ名が描画されてしまいます。 この場合は、WWWブラウザでアプレットを表示してください。

13.1.3 ガベージ・コレクション

Javaは高い信頼性を持つ言語です。 この信頼性を支える仕組みの一つに、 ガベージ・コレクションgarbage collection ) があります。

コンピュータのメモリ領域は有限ですので、大規模なデータ処理を行なう際には、不要なデータを破棄し、メモリを開放する必要が生じます。 多くのプログラミング言語では、この作業はプログラマの責任で行ないます。 しかし、不要なデータをいつまでも残してメモリ不足を引き起こしたり、誤った開放によって必要なデータを失ったりというバグを導きやすいのも事実です。

Javaは、この面倒な作業を自動的に行ないます。 これがガベージ・コレクションです。 ガベージ・コレクションでは、誰からも参照されないデータを不要なものと見なし、機械的にそれを検知してメモリを開放します。 機械的な部分で多少時間がかかりますが、バグの要因が減った分、信頼性の高いプログラムが書けるのです。

ガベージ・コレクションの働く例を以下に示します。 ここで ヒープheap ) とは、プログラムの実行中に確保されるメモリ領域のことです。

次のプログラムは、単にメモリを消費するものです。 Java言語では、 int 型のデータは4バイトと決まっていますので、2次元配列 a の消費するメモリは8×65536×4バイト、すなわち2MBです。

/*  1*/ class GCTest1 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         int[][] a = new int[8][];
/*  5*/         for (i = 0; i < 8; i++) {
/*  6*/             a[i] = new int[65536];
/*  7*/             System.out.println(i);
/*  8*/         }
/*  9*/     }
/* 10*/ }

Javaインタプリタは、デフォルトでは十分な(16MB)最大ヒープ領域を持ちますので、このプログラムは問題なく実行できます。

b00a001@AsiaA1:~/java% java GCTest1
0
1
2
3
4
5
6
7
b00a001@AsiaA1:~/java%

そこで、ヒープ領域を1MBに制限してみます。 オプションの ms は初期ヒープ領域、 mx は最大ヒープ領域を表します。 1MBのヒープ領域に2MBのデータは収まりませんので、プログラムは途中でエラーを起こして停止します。

b00a001@AsiaA1:~/java% java -ms1m -mx1m GCTest1
0
1
2
3
4
Exception in thread "main" java.lang.OutOfMemoryError
b00a001@AsiaA1:~/java%

次に、以下のようなプログラムを考えます。 これは上記のプログラムに似ています。 違いは、あらかじめ5行目で配列 b を用意しておき、それを8行目で a[i] に格納しなおすところです。

/*  1*/ class GCTest2 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         int[][] a = new int[8][];
/*  5*/         int[] b = new int[65536];
/*  6*/         for (i = 0; i < 8; i++) {
/*  7*/             a[i] = new int[65536];
/*  8*/             a[i] = b;
/*  9*/             System.out.println(i);
/* 10*/         }
/* 11*/     }
/* 12*/ }

配列 b がありますので、プログラムGCTest2はGCTest1よりメモリを消費するように思えます。 しかし、GCTest2は1MBのヒープ領域で実行できるのです。

b00a001@AsiaA1:~/java% java -ms1m -mx1m GCTest2
0
1
2
3
4
5
6
7
b00a001@AsiaA1:~/java%

この現象を理解するには、GCTest2の7行目と8行目に注目する必要があります。 7行目では、確かに65536要素の配列を生成し、それを a[i] に格納しています。 しかし、8行目で a[i] に配列 b を格納しなおしますので、7行目で生成した配列は誰も参照できなくなります。 誰も参照できないデータは記憶しておく必要がありませんので、Javaインタプリタはその領域を自動的に検知し、他のデータの記憶のために再利用するのです。


13.2 試験について

次回(1/8)の授業では試験を行います。 今日の演習問題をよく見直しておいてください。


13.3 成績評価について

レポートの提出に関するスケジュールは次の通りです。

1月15日(木)
全員にレポートの提出状況をメールで知らせます。
1月31日(土)
レポートの最終締め切り日です。 これ以降提出されたレポートは採点しません。

この授業の成績は、レポートの提出と試験の得点で決まります。

S
レポートはすべて提出。 「余力のある人」のための問題にも取り組んでいる。 試験の得点は「優」相当。
A
レポートはすべて提出。 試験の得点は「優」相当。
B
レポートはおおむね提出。 試験の得点は「良」相当。
C
レポートは未提出が目立つ。 試験の得点は「可」相当。
F
レポートは未提出が多い。 試験の得点は「不可」相当。

成績に関して次のような事情のある人はメールで連絡してください。 できる限り対処します。


13.4 演習13

問1. 次のプログラムは、入力された3つの整数の平均値(小数点以下切捨て)を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class Average3 {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int x, y, z, average;
/*  8*/         System.out.print("Enter a number: ");
/*  9*/         x = Integer.parseInt(br.readLine());
/* 10*/         System.out.print("Enter a number: ");
/* 11*/         y = Integer.parseInt(br.readLine());
/* 12*/         System.out.print("Enter a number: ");
/* 13*/         z = Integer.parseInt(br.readLine());
/* 14*/         average = (x + y) / 2;
/* 15*/         average = (average + z) / 2;
/* 16*/         System.out.println(average);
/* 17*/     }
/* 18*/ }
b00a001@AsiaA1:~/java% java Average3
Enter a number: 100
Enter a number: 80
Enter a number: 90
90 // 正しい
b00a001@AsiaA1:~/java% java Average3
Enter a number: 80
Enter a number: 90
Enter a number: 100
92 // 間違い
b00a001@AsiaA1:~/java%

問2. 次のプログラムは、入力された整数が 0 でなければある値を出力する(つもりの)ものです。 その値とは、入力を 10 増加させたものです。 ただし、増加させて 100 を越えるならば 100 を出力します。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class Plus10 {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int x;
/*  8*/         System.out.print("Enter a number: ");
/*  9*/         x = Integer.parseInt(br.readLine());
/* 10*/         if (x != 0) {
/* 11*/             x = x + 10;
/* 12*/             if (x > 100) {
/* 13*/                 System.out.println(100);
/* 14*/             }
/* 15*/         } else {
/* 16*/             System.out.println(x);
/* 17*/         }
/* 18*/     }
/* 19*/ }
b00a001@AsiaA1:~/java% java Plus10
Enter a number: 95
100 // 正しい
b00a001@AsiaA1:~/java% java Plus10
Enter a number: 85
b00a001@AsiaA1:~/java% // 出力がないので間違い

問3. 次のプログラムは、入力された3つの整数がすべて同じならば"Perfectly same."と出力し、すべて違うならば"Perfectly different."と出力し、どれでもないならば"Not perfect."と出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class SameDifferent {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int x, y, z;
/*  8*/         System.out.print("Enter a number: ");
/*  9*/         x = Integer.parseInt(br.readLine());
/* 10*/         System.out.print("Enter a number: ");
/* 11*/         y = Integer.parseInt(br.readLine());
/* 12*/         System.out.print("Enter a number: ");
/* 13*/         z = Integer.parseInt(br.readLine());
/* 14*/         if (x == y && y == z) {
/* 15*/             System.out.println("Perfectly same.");
/* 16*/         } else if (x != y && y != z) {
/* 17*/             System.out.println("Perfectly different.");
/* 18*/         } else {
/* 19*/             System.out.println("Not perfect.");
/* 20*/         }
/* 21*/     }
/* 22*/ }
b00a001@AsiaA1:~/java% java SameDifferent
Enter a number: 10
Enter a number: 10
Enter a number: 10
Perfectly same. // 正しい
b00a001@AsiaA1:~/java% java SameDifferent
Enter a number: 10
Enter a number: 20
Enter a number: 30
Perfectly different. // 正しい
b00a001@AsiaA1:~/java% java SameDifferent
Enter a number: 10
Enter a number: 20
Enter a number: 10
Perfectly different. // 間違い
b00a001@AsiaA1:~/java%

問4. 次のプログラムは、入力された正の整数を2で割り続け、割りきれなくなった(奇数になった)ときの値を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class HalfHalf {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int x, remainder = 0;
/*  8*/         System.out.print("Enter a number: ");
/*  9*/         x = Integer.parseInt(br.readLine());
/* 10*/         while (remainder == 0) {
/* 11*/             x = x / 2;
/* 12*/             remainder = x % 2;
/* 13*/         }
/* 14*/         System.out.println(x);
/* 15*/     }
/* 16*/ }
b00a001@AsiaA1:~/java% java HalfHalf
Enter a number: 100
25 // 正しい
b00a001@AsiaA1:~/java% java HalfHalf
Enter a number: 55
27 // 間違い
b00a001@AsiaA1:~/java%

問5. 次のプログラムは、{10, 20, 30, 40, 50} で表される配列を逆順に格納しなおして、配列要素を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ class Reverse {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, temp;
/*  4*/         int[] a = {10, 20, 30, 40, 50};
/*  5*/         for (i = 0; i < a.length; i++) {
/*  6*/             temp = a[i];
/*  7*/             a[i] = a[a.length - i - 1];
/*  8*/             a[a.length - i - 1] = temp;
/*  9*/         }
/* 10*/         for (i = 0; i < a.length; i++) {
/* 11*/             System.out.println(a[i]);
/* 12*/         }
/* 13*/     }
/* 14*/ }
b00a001@AsiaA1:~/java% java Reverse
10
20
30
40
50 // 逆順になっていないので間違い
b00a001@AsiaA1:~/java%

13.5 レポート課題

今日の演習13に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(12/18)を明記してください。


[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

2003年12月18日更新
konishi@twcu.ac.jp
Copyright (C) 2003 Zenjiro Konishi. All rights reserved.