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

コンピュータIIB(Javaプログラミング入門)第12回

目次
12.1 より高度な話題
12.1.1 バイトコードと仮想マシン
12.1.2 アプレット・セキュリティ
12.1.3 ガベージ・コレクション
12.2 今後の予定
12.3 成績評価について
12.4 演習12
12.5 レポート課題
12.6 参考文献
索引
Java仮想マシン   Javaバイトコード   インタプリタ   オブジェクトコード   ガベージ・コレクション   機械語   原始プログラム   高級言語   コンパイラ   ソースコード   ヒープ   目的プログラム  

12.1 より高度な話題

12.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コマンドでバイトコードを生成し、それをアプレット・ビューアやウェブ・ブラウザに読み込ませました。 アプレット・ビューアやウェブ・ブラウザには、バイトコード・インタプリタの機能が備わっていて、それが働いているのです。 アプレット・ビューアやウェブ・ブラウザの中には仮想マシンが内蔵されていると言うこともできます。

Java仮想マシンは、細かいところまで厳密に定義されています。 したがって、Javaプログラムはシステムの差を意識せずに書けるのです。

12.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*/ }
b04a001@AsiaA1:~/comp2b% java PropertyTest1
Mac OS X
b04a001@AsiaA1:~/comp2b%

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

次に、同じことをアプレットで行ないます。 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>
b04a001@AsiaA1:~/comp2b% appletviewer PropertyTest2.html &
b04a001@AsiaA1:~/comp2b%
アプレットビューア
Mac OS X
アプレットが開始されました。
図 12.1  OSの名前を描画するアプレット

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

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

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

12.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)最大ヒープ領域を持ちますので、このプログラムは問題なく実行できます。

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

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

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

次に、以下のようなプログラムを考えます。 これは上記のプログラムに似ています。 違いは、あらかじめ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のヒープ領域で実行できるのです。

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

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


12.2 今後の予定

今後の予定は以下の通りです。

7月21日(金)
授業中に試験を行います。 今日の演習問題をよく見直しておいてください。
7月28日(金)
この日までに、レポートの提出状況をメールで知らせます。
7月31日(月)
レポートの最終締め切り日です。 これ以降提出されたレポートは採点しません。

12.3 成績評価について

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

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

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


12.4 演習12

問1. 次のプログラムは、入力された2つの整数の和と積を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class SumProduct {
/*  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, result;
/*  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*/         result = x + y;
/* 13*/         result = x * y;
/* 14*/         System.out.println(result);
/* 15*/         System.out.println(result);
/* 16*/     }
/* 17*/ }
b04a001@AsiaA1:~/comp2b% java SumProduct
Enter a number: 2
Enter a number: 2
4 // 正しい
4 // 正しい
b04a001@AsiaA1:~/comp2b% java SumProduct
Enter a number: 1
Enter a number: 4
4 // 間違い
4 // 正しい
b04a001@AsiaA1:~/comp2b%

問2. 次のプログラムは、入力された3つの整数の中に、正の数がいくつ含まれているかを出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class HowManyPositives {
/*  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, count = 0;
/*  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 > 0) {
/* 15*/             count++;
/* 16*/         } else if (y > 0) {
/* 17*/             count++;
/* 18*/         } else if (z > 0) {
/* 19*/             count++;
/* 20*/         } else {
/* 21*/         }
/* 22*/         System.out.println(count);
/* 23*/     }
/* 24*/ }
b04a001@AsiaA1:~/comp2b% java HowManyPositives
Enter a number: 10
Enter a number: 0
Enter a number: 0
1 // 正しい
b04a001@AsiaA1:~/comp2b% java HowManyPositives
Enter a number: 0
Enter a number: 0
Enter a number: 0
0 // 正しい
b04a001@AsiaA1:~/comp2b% java HowManyPositives
Enter a number: 0
Enter a number: 10
Enter a number: 10
1 // 間違い
b04a001@AsiaA1:~/comp2b%

問3. 次のプログラムは、入力された3つの整数の最大値を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class MaximumNumber {
/*  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(z);
/* 16*/         } else if (x <= z && z <= y) {
/* 17*/             System.out.println(y);
/* 18*/         } else {
/* 19*/             System.out.println(x);
/* 20*/         }
/* 21*/     }
/* 22*/ }
b04a001@AsiaA1:~/comp2b% java MaximumNumber
Enter a number: 10
Enter a number: 20
Enter a number: 30
30 // 正しい
b04a001@AsiaA1:~/comp2b% java MaximumNumber
Enter a number: 30
Enter a number: 20
Enter a number: 10
30 // 正しい
b04a001@AsiaA1:~/comp2b% java MaximumNumber
Enter a number: 20
Enter a number: 30
Enter a number: 10
20 // 間違い
b04a001@AsiaA1:~/comp2b%

問4. 次のプログラムは、入力された2つの正の整数の最大公約数を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class CommonMeasure {
/*  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, m;
/*  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*/         m = x;
/* 13*/         while (x % m != 0 && y % m != 0) {
/* 14*/             m--;
/* 15*/         }
/* 16*/         System.out.println(m);
/* 17*/     }
/* 18*/ }
b04a001@AsiaA1:~/comp2b% java CommonMeasure
Enter a number: 20
Enter a number: 60
20 // 正しい
b04a001@AsiaA1:~/comp2b% java CommonMeasure
Enter a number: 40
Enter a number: 60
40 // 間違い
b04a001@AsiaA1:~/comp2b%

問5. 次のプログラムは、{10, 10, 10, 10, 10} で表される配列と、{20, 20, 20, 20, 20} で表される配列の、それぞれの要素の合計を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。

/*  1*/ class TwoArrays {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i, sum = 0;
/*  4*/         int[] a = {10, 10, 10, 10, 10};
/*  5*/         int[] b = {20, 20, 20, 20, 20};
/*  6*/         for (i = 0; i < a.length; i++) {
/*  7*/             sum = sum + a[i];
/*  8*/         }
/*  9*/         System.out.println(sum);
/* 10*/         for (i = 0; i < b.length; i++) {
/* 11*/             sum = sum + b[i];
/* 12*/         }
/* 13*/         System.out.println(sum);
/* 14*/     }
/* 15*/ }
b04a001@AsiaA1:~/comp2b% java TwoArrays
50 // 正しい
150 // 間違い
b04a001@AsiaA1:~/comp2b%

12.5 レポート課題

今日の演習12の答案(Javaプログラム)を、メールでkonishi@twcu.ac.jp宛に提出してください。 メールを送るときは、大学のパソコンを使うか、大学のメール・サーバに接続するかして、差出人が大学のメール・アドレスになるようにしてください。 メールの本文には、学生番号、氏名、科目名、授業日(7月14日)を明記してください。


12.6 参考文献


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

2006年7月14日更新
小西 善二郎 <konishi@twcu.ac.jp>
Copyright (C) 2006 Zenjiro Konishi. All rights reserved.