40.

PHP / Java のクラス再読み込み完全ガイド(OPcache, autoload, ClassLoader, ホットデプロイ)

編集
この記事の要点
  • PHP はリクエストごとに全クラスを再読み込みするが、本番は OPcache がバイトコードをキャッシュするためソース変更が即反映されない
  • OPcache の手動クリアは opcache_reset() / 個別ファイルは opcache_invalidate("/path/file.php")
  • Laravel では php artisan optimize:clear でルート/設定/ビュー/イベントの全キャッシュを一括クリア
  • Composer で新クラスを認識させるには composer dump-autoload -o(autoload マップ再生成)
  • Java は ClassLoader 単位で再読み込み: URLClassLoader を破棄して新規生成、または Spring DevTools / JRebel でホットデプロイ

「クラスの再読み込み」とは

サーバ側プログラムにおいて、編集したソースコードを再起動なしで反映させる仕組みを指します。言語・フレームワークごとに事情が異なります:

言語/環境デフォルト挙動キャッシュ層再読み込み手段
PHP(OPcache 無)毎リクエスト再読み込みなし不要
PHP(OPcache 有・本番)初回のみコンパイルバイトコードキャッシュopcache_reset()
Laravelconfig / route / view をキャッシュbootstrap/cacheartisan optimize:clear
Java (Tomcat 等)起動時に ClassLoader でロードJVM Method AreaClassLoader 再生成 / 再デプロイ
Node.jsrequire はキャッシュrequire.cachenodemon / delete require.cache[...]

PHP: OPcache のクリア

PHP 7+ では OPcache がデフォルト ON。本番でファイルを更新しても、OPcache の検証間隔(opcache.revalidate_freq)が経過するまで古いコードが動き続けることがあります。

本番サーバで反映するなら、Web からアクセスできる opcache-clear.php を一時的に置くか、CLI で cachetool を使います:

# cachetool で FPM の OPcache をクリア
cachetool opcache:reset --fcgi=127.0.0.1:9000

# php-fpm を reload する(USR2 シグナル)
sudo systemctl reload php8.2-fpm
# または
sudo killall -USR2 php-fpm

Laravel のキャッシュクリア

Laravel は OPcache に加え、設定/ルート/ビュー/イベントのキャッシュを独自に持ちます。クラスを追加・変更したら次のいずれかを実行:

# 全部まとめてクリア(推奨)
php artisan optimize:clear

# 個別
php artisan cache:clear      # アプリケーションキャッシュ
php artisan config:clear     # config キャッシュ
php artisan route:clear      # route キャッシュ
php artisan view:clear       # Blade コンパイル済キャッシュ
php artisan event:clear      # イベント/リスナーキャッシュ

# 新しいクラスを追加した時
composer dump-autoload -o

# 本番再構築(キャッシュを作り直す)
php artisan config:cache
php artisan route:cache
php artisan view:cache

Composer Autoload マップ再生成

新しいクラスを追加したが Class not found が出る場合、autoload マップに登録されていません:

# autoload.php と classmap を再生成
composer dump-autoload

# 本番向け最適化(classmap-authoritative)
composer dump-autoload -o --classmap-authoritative

# composer.json の autoload セクション例
# "autoload": {
#     "psr-4": { "App\\": "app/" },
#     "classmap": ["database/seeders", "database/factories"]
# }

Java: ClassLoader 単位の再読み込み

JVM は同じ ClassLoader で同名クラスを二重にロードできない仕様。再読み込みするには新しい ClassLoader を作るか、既存をまるごと破棄します:

import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

public class ReloadDemo {
    public static void main(String[] args) throws Exception {
        URL[] urls = { new java.io.File("plugins/").toURI().toURL() };

        // 1 回目のロード
        URLClassLoader loader1 = new URLClassLoader(urls);
        Class c1 = loader1.loadClass("com.example.Plugin");
        Method m1 = c1.getDeclaredMethod("run");
        m1.invoke(c1.getDeclaredConstructor().newInstance());
        loader1.close();  // 必須

        // JAR を入れ替えてから 2 回目のロード(新しい ClassLoader で)
        URLClassLoader loader2 = new URLClassLoader(urls);
        Class c2 = loader2.loadClass("com.example.Plugin");
        Method m2 = c2.getDeclaredMethod("run");
        m2.invoke(c2.getDeclaredConstructor().newInstance());
        loader2.close();
    }
}

Java: ホットデプロイの実用解

  • Spring Boot DevTools: spring-boot-devtools 依存追加で、classpath 変更を自動検知 → アプリ再起動(高速)
  • JRebel: 商用ツール。クラス/メソッド追加もホット反映
  • Tomcat Manager: WAR 再デプロイ用 API(/manager/text/reload
  • Jakarta EE 互換サーバ: WildFly / Payara は配布ディレクトリの WAR 入れ替えで自動再ロード

確認: 本当に新しいコードが動いているか

getFileName() . PHP_EOL;
echo $ref->getStartLine() . PHP_EOL;

// OPcache が見ているタイムスタンプを確認
$status = opcache_get_status(true);
foreach ($status['scripts'] as $file => $info) {
    if (str_contains($file, 'MyService.php')) {
        echo "timestamp=" . date('Y-m-d H:i:s', $info['timestamp']) . PHP_EOL;
    }
}

FAQ

Q: optimize:clear しても反映されない
A: PHP-FPM の OPcache は別プロセスで保持されています。sudo systemctl reload php-fpm も併用してください。

Q: 本番で OPcache を切ってよいか
A: 切ると性能が 3〜10 倍劣化します。opcache.validate_timestamps=1 + revalidate_freq=2 で運用が現実解。

Q: Java で java.lang.LinkageError: loader constraint violation
A: 2 つの ClassLoader が同名クラスを別々にロードしました。Class インスタンスを ClassLoader 境界をまたいでキャストするとこのエラーになります。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストールと設定
  2. クイックスタート & チュートリアル(初心者向け)
  3. クイックスタート & チュートリアル(中級者向け)
  4. ルーティング
  5. Bladeテンプレート(ビュー/レイアウト)
  6. コントローラー
  7. マイグレーションとテーブル定義
  8. データベースの設定
  9. Eloquentモデル (ORM)
  10. SQLとクエリビルダー
  11. バリデーション
  12. .envファイルの設定値へのアクセス
  13. 動作環境による分岐処理
  14. configフォルダ配下の設定値へのアクセス
  15. assetヘルパーを利用したpublicフォルダへのアクセス
  16. storageフォルダへのアクセス
  17. アプリケーション名の変更
  18. メンテナンス
  19. ログイン画面(認証システム)の作成
  20. ログインの必須化
  21. ログインユーザー情報の取得
  22. ルートの認証化
  23. 本番サーバーへのデプロイ方法
  24. 多言語化
  25. csrf_field
  26. ファイルのダウンロード
  27. CSVのアップロードおよび読み込み(maatwebsite/excel)
  28. ページタイトルの設定
  29. コマンド一覧
  30. エラー一覧
  31. SQLの実行ログ出力方法
  32. キャッシュのクリア
  33. Selectの結果の最初もしくは最後に任意の値を追加する方法
  34. ajaxでPOST通信する際の注意点
  35. ソーシャルログインの実装
  36. セッション情報の確認
  37. ログイン、ユーザー登録、パスワードリセット後のリダイレクト先の変更方法
  38. redirectやreturn viewにメッセージを付与する方法
  39. クッキー(cookie)の設定と取得
  40. クラスの再読み込み
  41. csrfの有効時間を変更する方法
  42. ViewComposerを用いてviewに共通の値を付与する方法
  43. View::shareを用いて共通の値を各ビューに渡す方法
  44. ミドルウェアを用いた処理の共通化
  45. Middleware内でAuth::check()などを使用する方法
  46. Controller以外でリダイレクトする方法
  47. セッションの値の取得/保存/更新/削除
  48. $requestの値を変更する方法
  49. 常時SSL化
  50. ページング(ページネーション)をする方法
  51. vue.jsとの連携
  52. Vue.jsと連携するSPA実行環境構築
  53. .envの値をvue.jsで参照する方法
  54. vue.jsを本番環境にリリースする方法
  55. could not find driver(Windows, MySQL編)