はじめに
Javaプログラムを書き始めたばかりの方にとって、「プログラムがどのように動いているのか」は謎に満ちています。この記事では、Javaプログラムが実行される瞬間から終了するまでの流れを、専門用語を使わずに分かりやすく解説します。
前提知識
この記事を読む前に知っておいてほしいこと:
- Javaの基本的な文法(クラス、メソッド、変数)
- コンパイルとは何か(.javaファイルから.classファイルを作ること)
プロセス1: プログラム開始とmainメソッド実行
🏁 何が起こっているの?
あなたがJavaプログラムを実行すると、コンピューターの中で以下のことが起こります:
- JVMが起動する
- mainメソッドを探して実行する
📖 詳しい説明
Javaプログラムを実行すると、コンピューターの中で「JVM」という特別なソフトウェアが起動します。
JVMって何?
- JVMは「Java Virtual Machine」の略
- Javaプログラムを実行するための専用のエンジン
- あなたのコンピューター上で動く「仮想的なコンピューター」のようなもの
mainメソッドの役割
JVMは必ず最初に「public static void main」というメソッドを探して実行します。これは決まりごとです。
public class HelloWorld {
public static void main(String[] args) { // ← ここから実行開始!
System.out.println("Hello, World!");
}
}
なぜmainメソッドから始まるの?
- Javaのプログラムには入り口が必要
- mainメソッドがその入り口の役割を果たす
- どんなJavaプログラムでも、mainメソッドが最初に実行されるスタートポイント
🔍 コンピューター内部で実際に起こっていること
上級者向けの技術的詳細:
-
クラスローディング: JVMがクラスローダーを使って
.classファイル(コンパイル済みのJavaコード)を読み込みます -
メソッド検証: mainメソッドのシグネチャ(
public static void main(String[] args))が正しいかチェック - メモリ割り当て: mainメソッド用のスタックフレームを作成
- 実行開始: プログラムカウンターが最初の命令を指して実行開始
プロセス2: 変数の宣言と初期化
🏁 何が起こっているの?
プログラムの中で変数を宣言すると、コンピューターのメモリ(記憶領域)に箱が用意されます。
📖 詳しい説明
public class CakeShop {
public static void main(String[] args) {
int cakeCount = 5; // ← ここで何が起こる?
String shopName = "Sweet Dreams"; // ← ここでも何が起こる?
}
}
変数宣言で起こること
-
メモリに箱を用意:
int cakeCountと書くと、整数を入れるための箱がメモリに作られます - 箱にラベルを貼る: その箱に「cakeCount」というラベル(名前)を付けます
-
値を箱に入れる:
= 5で、箱の中に「5」という値を入れます
データの種類による違い
- 基本データ型(int, double, booleanなど): 値そのものが箱に入る
- 参照データ型(String, 配列、オブジェクト): 実際のデータの住所(参照)が箱に入る
int number = 100; // 箱の中に「100」が直接入る
String text = "Hello"; // 箱の中に「"Hello"の住所」が入る
🔍 メモリの仕組み
スタック領域
- mainメソッド内の変数(ローカル変数)はここに作られる
- メソッドが終了すると自動的に削除される
- アクセスが高速
ヒープ領域
- オブジェクト(StringやArrayなど)の実際のデータはここに保存
- ガベージコレクションによって不要になったら削除される
プロセス3: メソッドの呼び出しと実行
🏁 何が起こっているの?
メソッドを呼び出すと、新しい「作業場」が用意されて、そこで処理が行われます。
📖 詳しい説明
public class Calculator {
public static void main(String[] args) {
int result = addNumbers(10, 20); // ← メソッド呼び出し
System.out.println(result);
}
public static int addNumbers(int a, int b) { // ← 呼び出されるメソッド
int sum = a + b;
return sum;
}
}
メソッド呼び出しのステップ
-
新しい作業場を用意:
addNumbersメソッド用の新しいスタックフレームを作成 - 引数をコピー: 呼び出し元の値(10, 20)を新しい作業場の変数(a, b)にコピー
-
メソッド内の処理を実行:
int sum = a + b;を実行 -
結果を返す:
return sum;で結果を呼び出し元に返す - 作業場を片付ける: メソッド終了後、スタックフレームを削除
スタックフレームって何?
- メソッド実行時に作られる「作業場」
- そのメソッド内で使う変数や情報を保存
- メソッドが終了すると自動的に削除される
🔍 呼び出しスタックの動作
[main メソッドのスタックフレーム]
├── result変数
└── addNumbers(10, 20)呼び出し
↓
[addNumbers メソッドのスタックフレーム] ← 新しく作られる
├── a = 10
├── b = 20
└── sum = 30
↓ return sum
↑ 30が返される
[main メソッドのスタックフレーム]
├── result = 30 ← 結果を受け取る
プロセス4: 条件分岐(if文)の処理
🏁 何が起こっているの?
if文では、条件を評価して「true」か「false」かを判断し、実行する処理を選択します。
📖 詳しい説明
public static void checkStock(int stock) {
if (stock > 0) { // ← 条件を評価
System.out.println("在庫があります");
} else {
System.out.println("在庫切れです");
}
}
条件分岐のステップ
-
条件式を評価:
stock > 0を計算してtrueまたはfalseを得る -
結果に基づいて分岐:
- trueなら: if文のブロック内を実行
- falseなら: else文のブロック内を実行(ある場合)
論理演算子の動作
if (age >= 18 && hasLicense) { // AND演算子
// 両方がtrueの場合のみ実行
}
if (isWeekend || isHoliday) { // OR演算子
// どちらか一方がtrueなら実行
}
プロセス5: 繰り返し処理(for文)の動作
🏁 何が起こっているの?
for文は「初期化 → 条件チェック → 処理実行 → 更新」のサイクルを繰り返します。
📖 詳しい説明
for (int i = 0; i < 5; i++) {
System.out.println("回数: " + i);
}
for文の実行順序
-
初期化:
int i = 0- カウンター変数iを0で初期化 -
条件チェック:
i < 5- 条件がtrueかfalseかをチェック -
処理実行:
System.out.println(...)- 条件がtrueなら中身を実行 -
更新:
i++- カウンター変数を更新(iを1増加) - 2に戻る: 条件がfalseになるまで繰り返し
実際の動作例「5未満(i < 5)」なので、5は含まれません。
1回目: i=0, 0<5はtrue → 処理実行 → i=1
2回目: i=1, 1<5はtrue → 処理実行 → i=2
3回目: i=2, 2<5はtrue → 処理実行 → i=3
4回目: i=3, 3<5はtrue → 処理実行 → i=4
5回目: i=4, 4<5はtrue → 処理実行 → i=5
6回目: i=5, 5<5はfalse → ループ終了
プロセス6: オブジェクトの生成と操作
🏁 何が起こっているの?
newキーワードを使うと、新しいオブジェクト(データのかたまり)がコンピューターのメモリに作られます。
📖 詳しい説明
まず、基本用語を理解しましょう:
フィールドとは?
- オブジェクトが持つ「データを入れる箱」のこと
- 例:ケーキオブジェクトなら「名前」「値段」がフィールド
初期化とは?
- 箱(フィールド)に最初の値を入れること
- 例:名前の箱に「チョコレートケーキ」、値段の箱に「1500」を入れる
コンストラクタとは?
- オブジェクトを作るときに自動で実行される特別なメソッド
- 箱を作って、最初の値を入れる作業を担当
public class Cake {
String name; // フィールド: ケーキ名を入れる箱
int price; // フィールド: 値段を入れる箱
public Cake(String name, int price) { // コンストラクタ
this.name = name; // この箱にケーキ名を入れる
this.price = price; // この箱に値段を入れる
System.out.println("👨🍳 新しいケーキを作成中: " + name);
}
}
// メインメソッド内
Cake cake = new Cake("チョコレートケーキ", 1500);
オブジェクト生成の詳細な流れ
Cake cake = new Cake("チョコレートケーキ", 1500); が実行されると、以下の順序で処理が進みます:
- 箱の準備: コンピューターがCakeオブジェクト用の箱を用意
-
コンストラクタ実行:
Cake(String name, int price)が自動で呼ばれる - 引数を受け取る: 「チョコレートケーキ」と「1500」がコンストラクタに渡される
- this.nameで保存: オブジェクトのname箱に「チョコレートケーキ」を保存
- this.priceで保存: オブジェクトのprice箱に「1500」を保存
- メッセージ表示: 「👨🍳 新しいケーキを作成中: チョコレートケーキ」が画面に表示
- 住所を記録: 完成したオブジェクトの場所をcake変数に保存
thisキーワードの役割:
- 「今作っているこのケーキオブジェクトの」という意味
- 引数の
nameとフィールドのnameを区別するために使う -
this.nameは「このケーキオブジェクトのname箱」を指す
つまり、new Cake()は「新しいケーキの箱を作って、名前と値段を入れて、その場所を教えて」という指示なのです。
🔍 コンピューター内部で実際に起こっていること
- ヒープメモリ確保: JVMがCakeオブジェクト用のメモリ空間を割り当て
- コンストラクタ実行: newによってコンストラクタが自動実行される
- 参照の返却: オブジェクトのアドレスがスタック変数に格納される
プロセス7: メソッド終了とメモリ解放
🏁 何が起こっているの?
メソッドが終了すると、そのメソッド用のスタックフレームが削除され、使用していたメモリが解放されます。
📖 詳しい説明
ローカル変数の削除
メソッドが終了すると、そのメソッド内で宣言された変数(ローカル変数)は自動的に削除されます。
public static void method() {
int localVar = 100; // ローカル変数
String localStr = "Hello"; // ローカル変数
// メソッド終了時、localVarとlocalStrは自動削除
}
ガベージコレクション
ヒープ領域のオブジェクトは、どの変数からも参照されなくなったときにガベージコレクションによって削除されます。
Student student = new Student("田中", 20);
student = null; // オブジェクトへの参照を切る
// この時点でStudentオブジェクトはガベージコレクションの対象
プログラム終了時の全体的な流れ
🏁 プログラム終了の瞬間
mainメソッドの最後の行が実行されると、Javaプログラムは終了します。
終了時に起こること
- スタック領域のクリア: 全てのスタックフレームが削除
- ヒープ領域のクリア: ガベージコレクションが全オブジェクトを削除
- JVMの終了: Java仮想マシンが終了
- OS制御に戻る: 制御がオペレーティングシステムに戻る
まとめ
Javaプログラムの実行は以下の流れで行われます:
- JVM起動 → mainメソッドを探して実行開始
- 変数宣言 → メモリに箱を用意して値を保存
- メソッド呼び出し → 新しい作業場を作って処理実行
- 条件分岐・繰り返し → 条件に基づいて処理の流れを制御
- オブジェクト操作 → ヒープ領域にデータを作成・操作
- メモリ解放 → 不要になった領域を自動削除
- プログラム終了 → 全メモリをクリアしてJVM終了
この流れを理解することで、なぜプログラムが意図した通りに動くのか、なぜエラーが発生するのかが見えてくるはずです。
次のステップ
この記事で基本的な流れを理解したら、次は以下を学習することをお勧めします:
- デバッガの使い方: プログラムの動作を一行ずつ確認
- メモリ管理: より詳細なメモリの使い方
- 例外処理: エラーが発生したときの対処方法
- マルチスレッド: 複数の処理を並行実行する方法
プログラミングは「コンピューターとの対話」です。コンピューターがあなたのコードをどのように理解し、実行しているかを知ることで、より良いプログラムが書けるようになります。