Skip to content

Javaの実行モデルと前提

Javaの実行モデルと、実務で事故を防ぐための前提条件を詳しく解説します。

実行モデルとリソースの物理的制約

Section titled “実行モデルとリソースの物理的制約”

コンピュータ資源は有限であり、性能ではなく制約を前提に設計することが基本です。

CPU・メモリよりも先に枯渇するリソース:

  1. DB・外部APIのコネクション数

    • 接続プールの上限(例: HikariCPのmaximumPoolSize
    • 接続リークは数時間後にシステム全体を停止させる
  2. スレッドプール

    • ExecutorServiceのスレッド数制限
    • スレッド枯渇により、すべてのリクエストが処理できなくなる
  3. ファイル記述子

    • OSレベルの制限(通常1024〜65536)
    • ファイルやソケットを適切にクローズしないと枯渇

実際の事故例:

10:00:00 - アプリケーション起動(接続プール: 10/10)
10:00:01 - リクエスト1受信(接続取得: 11/10 → 待機)
10:00:02 - リクエスト2受信(接続取得: 12/10 → 待機)
...
10:30:00 - 接続が解放されず、すべてのリクエストが待機状態
10:30:01 - タイムアウトエラーが大量発生
10:30:02 - システム全体が応答不能

実行モデル:

Javaソースコード (.java)
↓ コンパイル
Javaバイトコード (.class)
↓ JVMで実行
ネイティブコード(実行時)

重要な特徴:

  1. コンパイル時型チェック: コンパイル時に型エラーを検出
  2. ガベージコレクション: 自動メモリ管理(ただし、参照が保持されている場合は動作しない)
  3. マルチスレッド: ネイティブスレッドサポート(スレッドプールの管理が必要)
  4. 例外処理: チェック例外と非チェック例外(適切なエラーハンドリングが必要)

Javaのトランザクション管理:

// Spring Frameworkでのトランザクション管理
@Transactional
public void createOrder(OrderData orderData) {
// トランザクション内の処理
Order order = orderRepository.save(new Order(orderData));
inventoryService.reduceStock(order.getItems());
paymentService.chargePayment(order.getId(), orderData.getAmount());
// すべて成功するか、すべてロールバック
}

特徴:

  • 明示的なトランザクション境界: @Transactionalアノテーションで明示
  • 宣言的トランザクション管理: AOPによる自動管理
  • トランザクション伝播: REQUIRED, REQUIRES_NEW, NESTEDなど

Javaの非同期処理:

// CompletableFutureを使用
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return externalApi.call();
});
// 非同期処理は信頼できるが、エラーハンドリングが必要
future.thenAccept(result -> {
// 成功時の処理
}).exceptionally(error -> {
// エラー時の処理
return null;
});

特徴:

  • 信頼できる非同期: CompletableFutureExecutorServiceによる制御
  • エラーハンドリング: 明示的なエラー処理が必要
  • 再実行: 手動で実装する必要がある
環境特徴主なリスク
Serverless (Lambda/Vercel)短寿命・自動スケールコールドスタート、接続バースト、DBパンク、実行時間制限(Lambda: 15分、Vercel: 300秒)
常駐プロセス (Spring Boot)長寿命・安定動作メモリリーク、プール断片化、デッドロック、接続リーク

制約:

// ❌ 悪い例: Serverless環境で問題のあるコード
@RestController
public class OrderController {
@PostMapping("/orders")
public Order createOrder(@RequestBody OrderData orderData) {
// 問題: 長時間実行される可能性がある
// 問題: トランザクションが長時間保持される
// 問題: 接続プールが適切に管理されない
return orderService.createOrder(orderData);
}
}

問題点:

  • 実行時間の制限: Lambdaは最大15分、Vercelは最大300秒
  • コールドスタート: JVMの起動に時間がかかる(1-3秒)
  • メモリ制限: メモリ使用量に制限がある(Lambda: 128MB〜10GB)
  • 接続バースト: スケールアウト時に接続プールが急増し、DBがパンクする可能性

解決策:

// ✅ 良い例: Serverless環境に適したコード
@RestController
public class OrderController {
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderData orderData) {
// 1. バリデーション(短時間)
validateOrderData(orderData);
// 2. 注文を作成(短時間)
Order order = orderService.createOrder(orderData);
// 3. 非同期処理をキューに投入
messageQueue.send("order.created", order.getId());
// 4. 即座にレスポンスを返す
return ResponseEntity.accepted()
.body(new OrderResponse(order.getId(), "PROCESSING"));
}
}

特徴:

// 常駐プロセス環境での実行
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

メリット:

  • 長時間実行可能: 実行時間の制限がない
  • 接続プール: データベース接続プールを保持
  • キャッシュ: メモリキャッシュを保持
  • バックグラウンド処理: @Scheduled@Asyncによる処理

実装例:

// ✅ 良い例: 常駐プロセス環境に適したコード
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private PaymentService paymentService;
@Transactional
public Order createOrder(OrderData orderData) {
// トランザクション内で処理
Order order = orderRepository.save(new Order(orderData));
paymentService.chargePayment(order.getId(), orderData.getAmount());
return order;
}
@Async
public CompletableFuture<Void> processOrderAsync(Long orderId) {
// 非同期処理
return CompletableFuture.runAsync(() -> {
// 長時間実行される処理
processOrder(orderId);
});
}
}

Javaの実行モデルと前提のポイント:

  • リソースの物理的制約: CPU・メモリよりも先に枯渇するのは、DB接続数・スレッドプール・ファイル記述子
  • JVM: コンパイル時型チェック、ガベージコレクション、マルチスレッド
  • トランザクション境界: @Transactionalで明示、宣言的管理
  • 非同期処理: CompletableFutureによる制御、エラーハンドリングが必要
  • Serverless環境: 実行時間制限、コールドスタート、メモリ制限、接続バースト
  • 常駐プロセス環境: 長時間実行可能、接続プール、キャッシュ、バックグラウンド処理(メモリリーク・接続リークに注意)

重要な原則: 性能ではなく制約を前提に設計する。リソースの垂れ流しは数時間後にシステム全体を停止させる。