JavaとVB.NETはどっちが速い?パフォーマンス性能の差やWeb・デスクトップアプリ開発での最適な使い分け

JavaとVB.NETのロゴを背景に、速度計とサーバー・デスクトップのアイコンが並んだ対比イメージ プログラミング言語

「JavaとVB.NET、どちらが速いのか?」――この問いは、実務で両言語を扱うエンジニアの間で繰り返し議論されてきました。
しかし、この質問に単純な二択で答えることはできません。
なぜなら、実行環境処理の特性がパフォーマンスを大きく左右するからです。

まず、JavaはJVM(Java仮想マシン)上で動作し、JIT(Just-In-Time)コンパイラが実行中にホットスポットを機械語に最適化します。
一方、VB.NETは.NET Frameworkまたは.NET Core上で動作し、同じくJITコンパイルを行います。
理論上のピーク性能は、どちらもネイティブコードに近い水準に達しますが、そのプロセスに違いがあります。

  • Javaは起動時にクラスローディングと初期JITコンパイルのオーバーヘッドが発生するため、短命なバッチ処理ではVB.NETに後れを取ることがあります
  • VB.NETは.NETランタイムが事前にアセンブリをNgen(ネイティブイメージ生成)できるため、初回起動が高速になるケースがあります
  • しかし、長時間稼働するサーバーアプリでは、JavaのJITが実行統計を基に積極的なインライン展開やループアンローリングを行うため、処理が進むほどJavaが優位になる傾向があります

では、具体的なユースケースで見てみましょう。
Webアプリケーション開発では、JavaはSpring BootやQuarkusなどの軽量フレームワークが充実し、マイクロサービスや高負荷なAPIサーバーで実績があります。
VB.NETはASP.NET Web FormsやMVCと組み合わせられますが、近年はC#への移行が進んでおり、新規プロジェクトでは選択肢として劣ります。
デスクトップアプリでは、VB.NETはWindows FormsやWPFとの親和性が高く、RAD(高速アプリケーション開発)に適していますが、JavaのSwingやJavaFXはクロスプラットフォーム性で勝ります。

比較項目 Java VB.NET
実行モデル JVM + JITコンパイル .NET CLR + JIT(Ngen可)
起動速度 やや遅い 比較的速い
長時間性能 向上する 安定しているが頭打ち
マルチスレッド 標準で強力 非同期/タスク並列で対応
クロスプラットフォーム 優れる(Windows/Linux/macOS) 限定的(.NET Coreで改善中)

結論として、「どちらが速いか」ではなく、「何を重視するか」 で選ぶべきです。
バッチ処理やマイクロサービス、大規模分散システムにはJavaを。
社内用の業務アプリやWindows環境で短期開発するにはVB.NETを選ぶのが現実的です。
また、パフォーマンスチューニングの観点では、言語自体よりもGC(ガベージコレクション)の挙動メモリ割り当てパターンデータベースアクセスの回数がボトルネックになることを忘れてはいけません。
たとえば、Javaで100万件のループ処理を書く場合、プリミティブ型を使うかラッパー型を使うかで速度が数倍変わります。
VB.NETでも同様に、For EachよりForループの方が高速なケースがあります。

結局、両言語とも現代的で高性能なランタイムを持ちます。
重要なのは、プロジェクトの要件、運用環境、開発チームのスキルセットを総合的に判断することです。
パフォーマンスだけに惑わされず、保守性や拡張性も含めたトレードオフを冷静に見極めてください。

  1. JavaとVB.NETのパフォーマンスを語る前に:実行モデルの根本的な違い
    1. Javaの実行モデル:バイトコード+JITコンパイル
    2. VB.NETの実行モデル:IL+JITまたは事前コンパイル
    3. ガベージコレクションとメモリ管理の違い
  2. 起動速度とウォームアップ:短命プロセスではどちらが有利か
    1. 起動フェーズの内訳を分解する
    2. ウォームアップ時間と実効速度のトレードオフ
    3. コード例で見る実測的な差
    4. 実務での判断基準
  3. 長時間稼働時のスループット:JIT最適化の実力差
    1. プロファイル駆動最適化の威力
    2. スループット推移の実測パターン
    3. コード例:JITが効く典型的なパターン
    4. ピーク性能に影響を与える副次的要因
  4. メモリ管理とGCの挙動がパフォーマンスに与える影響
    1. JavaのGC戦略:選択肢の多様性とリージョンベース管理
    2. .NETのGC:セグメントベースとモード選択
    3. メモリフラグメンテーションとオブジェクトレイアウトの差異
    4. 実コードにおけるGC負荷の例
    5. 実務におけるGC選定の指針
  5. Webアプリ開発における実効性能:フレームワークと同時接続処理
    1. フレームワークの特性とオーバーヘッド
    2. 同時接続処理モデルの比較
    3. データベースアクセスとの連携における性能差
    4. デプロイ環境とスケーリング戦略の影響
  6. デスクトップアプリ開発での使い分け:UI応答性とリソース消費
    1. UIフレームワークの構造とイベントディスパッチ
    2. メモリ消費と起動後のフットプリント
    3. UI応答性とバックグラウンド処理の連携
    4. 実務での選択基準
  7. バッチ処理やバックグラウンドジョブではどちらを選ぶべきか
    1. バッチ処理に求められる三つの要件
    2. Javaのバッチ処理エコシステム
    3. VB.NETのバッチ処理アプローチ
    4. 実処理速度の比較:ファイルI/OとDBアクセス
    5. 運用面での考慮点
    6. 最終的な選択基準
  8. 総合評価とプロジェクト別最適選択:パフォーマンスだけではない判断基準
    1. パフォーマンス以外に考慮すべき五つの軸
    2. プロジェクトタイプ別の最適選択マトリクス
    3. 移行コストと既存資産の活用
    4. 最終的な判断フレームワーク

JavaとVB.NETのパフォーマンスを語る前に:実行モデルの根本的な違い

JavaのJVMとVB.NETのCLRという異なる実行環境のアーキテクチャ比較図

処理速度の比較に入る前に、まず両言語がどのような実行モデルで動作しているのかを正確に理解しておく必要があります。
なぜなら、パフォーマンスとは言語仕様そのものよりも、実行環境とコンパイル戦略に依存する部分が極めて大きいからです。
JavaとVB.NETはどちらも中間言語を経由するマネージド言語ですが、その中間表現と実行時最適化の仕組みが異なります。

Javaの実行モデル:バイトコード+JITコンパイル

Javaのソースコードは、まずjavacコンパイラによってバイトコード(.classファイル)に変換されます。
このバイトコードはJava仮想マシン(JVM)上で解釈実行されることを前提としており、プラットフォームに依存しない中間表現です。
JVMはこのバイトコードを、起動時にインタプリタ方式で読み込みながら実行を開始します。
ただし、それだけでは速度が出ないため、JVMはホットスポット検出という仕組みを持っています。

  • メソッドやループが頻繁に呼び出されると、JVMはその部分を「ホットスポット」と判定します
  • ホットスポットに対して、JITコンパイラがバイトコードをネイティブマシン語に動的コンパイルします
  • さらに、階層型コンパイル(C1コンパイラとC2コンパイラ)により、実行統計を蓄積しながら段階的に最適化を深めます

このアプローチの特徴は、実行開始直後はインタプリタ動作のため遅いが、時間経過とともに最適化が進みピーク性能が高まるという点です。
また、プロファイル情報に基づいて、デッドコード除去やインライン展開、ループアンローリングなどの積極的な最適化がかかるため、理論上の最高速度は非常に高い水準に達します。

VB.NETの実行モデル:IL+JITまたは事前コンパイル

一方、VB.NETは.NETランタイム(CLR:Common Language Runtime)上で動作します。
ソースコードはVisual Basicコンパイラによって中間言語(IL:Intermediate Language) に変換され、アセンブリ(.exeまたは.dll)として出力されます。
このILもJavaのバイトコードと似た中間表現ですが、実行時には.NETのJITコンパイラがILをネイティブコードに変換します。

しかし、VB.NETにはJavaにはない選択肢があります。
.NET Framework時代から存在するNgen(Native Image Generator) や、.NET Core以降のCrossgen2/ReadyToRun(R2R) 機能を用いると、アセンブリを事前にネイティブイメージとして出力できます。
この場合、アプリケーション起動時にJITコンパイルが不要になるため、初回起動が大幅に高速化されます。

  • 事前コンパイルイメージは、特定のCPUアーキテクチャとランタイムバージョンに依存します
  • ただし、事前コンパイルでは実行時のプロファイル情報が使えないため、Javaの動的JITほど高度な最適化は適用されません
  • そのため、起動直後はVB.NETがリードするが、長時間稼働ではJavaが追い越すという逆転現象が発生し得ます

この違いは、単なる「どちらが速いか」ではなく、アプリケーションのライフサイクルに直結する要素です。
たとえば、週に1回実行するバッチ処理なら起動速度が重要ですが、24時間稼働するマイクロサービスならピークスループットが優先されます。

ガベージコレクションとメモリ管理の違い

もう一つ無視できないのは、メモリ管理戦略の差異です。
JavaのGCはデフォルトでG1GC(またはZGCやShenandoah)など、スループットよりレイテンシ安定性を重視した世代別コレクションを採用しています。
これに対し、.NETのGCはワークステーションGCとサーバーGCの2モードがあり、サーバーモードではマルチスレッド並列回収により大規模ヒープでのスループットを優先します。

この違いは、オブジェクトの生成頻度や寿命パターンによって、顕著な性能差を生みます。
たとえば、短命オブジェクトを大量に生成するWebアプリでは、JavaのG1GCがリージョン単位で効率的に回収する一方、.NETのサーバーGCはセグメント単位のコンパクションを行うため、ヒープ断片化のリスクが異なります。
また、両者ともメモリバリアや書き込みバリアの実装が異なるため、マルチスレッド環境下でのロックフリーアルゴリズムの挙動にも差が出ます。

このように、実行モデルの中核をなすコンパイル戦略とGC設計が、両言語のパフォーマンス特性を根本から形作っています。
次の章からは、具体的なユースケースごとに、これらの要素がどのように実効速度に影響するのかを掘り下げていきます。

起動速度とウォームアップ:短命プロセスではどちらが有利か

JavaのクラスローディングとJITコンパイルによる起動遅延を時系列で示したグラフ

パフォーマンス議論で最初にぶつかる壁が「起動速度」です。
特に、バッチ処理やコマンドラインツール、CI/CDパイプライン内のワンオフスクリプトなど、プロセス寿命が数秒から数分で終了する短命アプリケーションでは、起動時間が総処理時間に直結します。
この領域において、JavaとVB.NETは明確に異なるプロファイルを示します。

起動フェーズの内訳を分解する

まず、両言語の起動プロセスを構成要素ごとに分解してみましょう。
Javaの場合、JVMの起動には以下のステップが含まれます。

  • クラスローダーによるブートストラップクラスパスとシステムクラスパスのスキャン
  • 初期ヒープサイズの確保とGCサブシステムの初期化
  • エントリポイントクラスのロードとリンク(解決・検証・準備)
  • メインメソッドのインタプリタ実行開始(JITが効くまではバイトコード解釈)

これらのフェーズは、特にSpring Bootのような大型フレームワークを使うと、クラスパス上の数百〜数千のクラスをスキャンするため、起動だけで数秒から十数秒かかることも珍しくありません。
一方、素のJavaでHelloWorld程度なら数百ミリ秒で終わりますが、実用的なアプリではそうもいきません。

VB.NETの場合はどうでしょうか。
.NETランタイムも同様にアセンブリロードやJIT初期化を行いますが、以下の点で有利です。

  • .NET FrameworkではGAC(グローバルアセンブリキャッシュ)により共有アセンブリが事前にネイティブイメージ化されている場合がある
  • .NET Core以降はReadyToRun(R2R)イメージを使えば、JITコンパイルを完全にスキップしてネイティブコードを直接実行可能
  • アセンブリ単位の遅延ロードが可能で、実際に呼び出されるメソッドだけをJIT対象にできる

そのため、同じ規模のアプリケーションでも、VB.NETはJavaよりも一貫して起動が速いという傾向が観測されます。
特にR2Rを有効にした.NETアプリでは、起動時間がJavaの半分以下になるケースが多々あります。

ウォームアップ時間と実効速度のトレードオフ

しかし、起動速度だけが全てではありません。
ここで重要なのが「ウォームアップ」という概念です。
JavaのJITは実行中にプロファイルを収集し、最適化を段階的に進めるため、最初の数秒から数十秒はインタプリタまたは低レベル最適化(C1)で動き、真のパフォーマンスはその後(C2コンパイル後)に発揮されます。

フェーズ Java(Spring Boot) VB.NET(R2R有効)
起動完了までの時間 3〜8秒(平均) 0.5〜2秒
ピーク性能到達までの時間 30〜60秒(ウォームアップ要) ほぼ即時(事前最適化済み)
短命処理(5秒未満)でのスループット 低い(インタプリタ主体) 高い(ネイティブ実行)

この表から分かるように、プロセス寿命が短いほどVB.NETが有利であり、Javaはウォームアップの恩恵を受けられずに終わってしまいます。
たとえば、1回あたりの実行時間が3秒のバッチジョブを1日100回実行するようなケースでは、Javaは毎回インタプリタ状態で走るため、VB.NETのR2R実行と比較して総処理時間で2倍以上の差がつく可能性があります。

コード例で見る実測的な差

実際のコードで比較してみましょう。
単純な整数のループ加算を行う処理を考えます。

// Java
public class LoopBenchmark {
    public static void main(String[] args) {
        long sum = 0;
        for (int i = 0; i < 10_000_000; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}
' VB.NET
Module LoopBenchmark
    Sub Main()
        Dim sum As Long = 0
        For i As Integer = 0 To 10000000 - 1
            sum += i
        Next
        Console.WriteLine(sum)
    End Sub
End Module

このコードを素の状態で実行した場合、VB.NET(R2R有効)は初回実行で約80ミリ秒Java(JIT未ウォーム)は約200ミリ秒かかることがあります。
ただし、同じJavaのコードを10回連続実行すると、3回目以降はJITが効いて50ミリ秒を切るようになります。
つまり、短命ではVB.NET、長命ではJavaという構図がここでも現れます。

実務での判断基準

では、短命プロセスにおいては常にVB.NETを選ぶべきかと言うと、そう単純ではありません。
なぜなら、JavaにもGraalVM Native Imageという選択肢が登場したからです。
これはAOT(Ahead-Of-Time)コンパイルにより、Javaアプリをネイティブ実行可能ファイルに変換する技術で、起動速度をミリ秒単位にまで短縮します。
これを使えば、JavaでもVB.NETと同等以上の起動性能を達成できます。
ただし、リフレクションや動的クラスローディングに制約が生じるため、すべてのJavaアプリに適用できるわけではありません。

結論として、短命プロセスではVB.NET(特にR2RまたはNgen)がデフォルトで有利ですが、将来的な移植性やエコシステムの広さを考慮するなら、Java+GraalVMという組み合わせも有力な選択肢です。
重要なのは、自分のアプリケーションの平均プロセス寿命許容できるウォームアップ時間を明確に定義した上で選定することです。
単なる「起動が速い=良い」ではなく、総合的な運用コストを見極める視点が求められます。

長時間稼働時のスループット:JIT最適化の実力差

サーバー連続稼働時のJavaとVB.NETのスループット推移を比較した折れ線グラフ

起動速度で後れを取るJavaですが、アプリケーションが長時間稼働し、ウォームアップを完了した後には全く異なる顔を見せます。
ここでは、連続稼働が数時間から数日に及ぶサーバーアプリケーションや常駐バックグラウンドプロセスにおいて、両言語のスループットがどのように推移するのかを詳細に比較していきます。

プロファイル駆動最適化の威力

JavaのJITコンパイラ、特にC2(Server Compiler)は、プロファイルフィードバックを基にした積極的な最適化が最大の特徴です。
実行中に収集された分岐予測情報、メソッド呼び出し回数、ループ反復数、さらにはガベージコレクションの負荷パターンまでもが最適化に反映されます。

  • インライン展開:頻繁に呼ばれる小さなメソッドを呼び出し元に埋め込み、コールオーバーヘッドを排除します。場合によっては5層以上のネストインラインも行われます
  • ループアンローリングとベクトル化:定数回数のループを展開し、SIMD命令(AVXやSSE)の活用を試みます。これにより、数値演算系の処理で2〜4倍の速度向上が見込めます
  • デッドコード除去と定数畳み込み:実行時に決して到達しない分岐や、常に同一値となる計算をコンパイル時に削除します
  • ロックの省略(Lock Elision):スレッドローカルなオブジェクトに対する同期化を、安全と判断された場合に除去します

これらの最適化は、C2が約1万回のメソッド呼び出しまたは約1万回のループ反復を閾値として発動されます。
つまり、ウォームアップが完了した後のJavaアプリは、静的コンパイル言語に匹敵する、あるいはそれを上回るネイティブコードに変換されているのです。

一方、VB.NETのJIT(RyuJIT)も優れた最適化を行いますが、実行時プロファイルを蓄積して動的に最適化を深める仕組みは標準では備わっていません
.NETのJITは、メソッド単位で最初の呼び出し時にコンパイルされ、その後は再コンパイルされません(例外として、Tiered Compilationが.NET Core 3.0以降で導入されましたが、JavaのC2ほどの深い最適化は行われません)

スループット推移の実測パターン

ここで、両言語のスループット推移を時系列で模式的に表してみます。
対象は、JSONシリアライゼーションとデータベースアクセスを繰り返す典型的なWeb APIとします。

経過時間 Java(スループット:req/sec) VB.NET(スループット:req/sec)
0〜1分(起動直後) 約2,000 約4,500(R2R有利)
1〜5分(ウォーム中) 約3,500→8,000 約4,700(ほぼ一定)
5〜30分(安定後) 約12,000 約5,000
1時間以降 約13,000(さらに微増) 約5,100(微増)

この表からわかる通り、Javaは時間経過とともにスループットが向上し、最終的にはVB.NETの2倍以上に達するケースが珍しくありません。
特に、複雑なビジネスロジックや大量のオブジェクト生成を伴う処理では、この差はさらに拡大します。

コード例:JITが効く典型的なパターン

次のような、頻繁に呼び出されるメソッドがあるとします。

public double compute(double[] data) {
    double sum = 0.0;
    for (int i = 0; i < data.length; i++) {
        sum += Math.sqrt(data[i]) * Math.log(data[i] + 1);
    }
    return sum / data.length;
}

このメソッドが1秒間に数万回呼び出される場合、JavaのJITはMath.sqrtMath.logのネイティブ実装への呼び出しをインライン化し、さらにループ内の境界チェックを除去します。
また、data.lengthが不変であると判断すれば、ループ回数を固定値として扱うこともあります。
結果として、ウォームアップ後はインタプリタ実行時の5〜10倍の速度で動作します。

VB.NETでも同様の最適化は行われますが、再コンパイルがないため、初回コンパイル時の前提が変わらない限り速度は頭打ちになります。
つまり、負荷パターンが変化するマルチテナント環境では、Javaの動的適応性が大きなアドバンテージとなります。

ピーク性能に影響を与える副次的要因

スループットには、JIT以外にも以下の要素が影響します。

  • GCスレッドの優先度:JavaのZGCやShenandoahは、アプリケーションスレッドと並行してGCを行うため、ピーク時でも一時停止が数ミリ秒に抑えられます。.NETのサーバーGCも並行処理をサポートしますが、ヒープサイズが大きくなるとコンパクション時の停止時間がJavaより長くなる傾向があります
  • ロック実装の効率:Javaのjava.util.concurrentパッケージは、アンセーフAPIを使った低レベルなスピンロックやキューベースのロックを提供しており、高競合下で.NETのMonitorクラスよりもスケーラブルです
  • メモリバリアの頻度:Javaのメモリモデルは、揮発性変数に対するバリア挿入が最適化されており、x86アーキテクチャでは多くの場合に不要なバリアが省略されます。.NETも同様ですが、やや保守的なバリア挿入が見られます

これらの総合効果により、長時間稼働するミッションクリティカルなバックエンドでは、Javaが圧倒的な選択肢となります。
実際、大規模な金融システムや通信基盤でJavaが採用され続けているのは、このピーク性能の高さと安定性に裏付けられています。

ただし、VB.NETがすべての長期稼働で劣るわけではありません。
IIS上で動作するASP.NETアプリケーションでは、アプリケーションプールのリサイクルが定期的に発生するため、Javaほどの長時間ウォームアップを活かせない場合もあります。
そのため、実際の運用パターン(再起動頻度、トラフィック変動)を加味した上で評価することが不可欠です。
単純な「どちらが速い」ではなく、「自分のシステムの稼働プロファイルにどちらが適合するか」で判断すべきテーマだと言えるでしょう。

メモリ管理とGCの挙動がパフォーマンスに与える影響

JavaのG1GCと.NETのワークステーションGCによるメモリ解放タイミングの模式図

処理速度を語る上で、メモリ管理、特にガベージコレクションの振る舞いを無視することはできません。
JavaとVB.NETはどちらもトレース型GCを採用していますが、そのアルゴリズムとチューニングの柔軟性には根本的な設計哲学の違いが存在します。
この違いが、特にメモリ負荷の高いアプリケーションにおいて、スループットや応答遅延に直接的な影響を与えます。

JavaのGC戦略:選択肢の多様性とリージョンベース管理

JavaのGCは、長年の進化を経て複数の実装が提供されています。
現行のLTSバージョンでは、G1GCがデフォルトですが、ZGCやShenandoahのような低レイテンシGCも実用段階に達しています。
これらの共通した特徴は、ヒープを複数のリージョンに分割し、生きているオブジェクトだけを選択的にコピーする方式を取っている点です。

  • G1GC(Garbage-First)は、リージョン単位で回収優先度を付け、最大一時停止時間の目標を設定できます。これにより、アプリケーションスレッドを止める時間を数十ミリ秒単位に抑えつつ、スループットも確保します
  • ZGCは、並行処理をさらに徹底し、一時停止を1ミリ秒未満にまで短縮します。ただし、その代わりにCPUリソースを多く消費するトレードオフがあります
  • Shenandoahは、ZGCと似た並行コンパクションを実装し、大規模ヒープでの応答性に優れます

これらのGCはすべて、アプリケーションの特性に合わせてアルゴリズムを切り替えられるという大きなアドバンテージを持っています。
たとえば、バッチ処理ではスループット優先のParallel GCを選び、WebサーバーではG1GC、金融取引システムではZGCを選ぶ、といった使い分けが可能です。

.NETのGC:セグメントベースとモード選択

一方、VB.NETが動作する.NETランタイムのGCは、セグメントベースの世代別マークコンパクションを基本とします。
ヒープを0世代(若いオブジェクト)、1世代(中間)、2世代(古いオブジェクト)に分割し、世代が若いほど頻繁に回収を行います。
.NETのGCには以下の2つのモードが存在します。

  • ワークステーションGC:デスクトップアプリ向けで、スレッド数が少なく、応答性を重視します。一時停止は短いですが、スループットは控えめです
  • サーバーGC:マルチコア環境向けで、ヒープを複数のセグメントに分割し、各コアが並行してマークとコンパクションを行います。スループットは高いですが、フルGC時の一時停止が数秒に達することもあります

.NETのGCは、Javaと比較してチューニングパラメータが少ないという特徴があります。
主な調整項目は、gcServerフラグとgcConcurrentフラグ、そして世代のしきい値サイズ程度です。
そのため、特定のワークロードに合わせた微調整はJavaほど柔軟ではありません。

メモリフラグメンテーションとオブジェクトレイアウトの差異

パフォーマンスに影響を与えるもう一つの要素は、ヒープ内のオブジェクト配置フラグメンテーションです。
JavaのG1GCやZGCは、リージョン単位でオブジェクトをコピーするため、コンパクション時にフラグメンテーションがほぼ発生しません。
しかし、その代わりにポインタ書き換えのオーバーヘッドが発生します。

.NETのサーバーGCは、セグメント単位でコンパクションを行いますが、大きなオブジェクト(85KB以上)はLOH(Large Object Heap)に配置され、コンパクションされずに解放されるだけです。
そのため、大量の大きなオブジェクトを生成・破棄するアプリケーションでは、LOHのフラグメンテーションが進行し、メモリ使用効率が低下して最終的にOutOfMemoryに至るリスクがあります。

GCの特徴 Java(G1GC) .NET(サーバーGC)
ヒープ分割単位 リージョン(1〜32MB) セグメント(数十MB〜数百MB)
コンパクション方式 リージョン内コピー(並行可) セグメント単位(停止あり)
大規模オブジェクト処理 フンディングリージョンで管理 LOH(非コンパクション)
チューニングパラメータ数 50以上 10前後
一時停止目標設定 可能(ミリ秒単位) 不可(固定動作)

実コードにおけるGC負荷の例

実際に、オブジェクトを大量に生成する処理を考えます。

// Java
List<String> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    list.add("Item" + i);
    if (i % 10000 == 0) {
        list.clear(); // 短命オブジェクトの解放を促す
    }
}

このコードでは、JavaのG1GCがリージョン単位で短命オブジェクトを効率的に回収します。
clear()が呼ばれるたびに、そのリージョン内のオブジェクトが一括で死んでいることを検出し、ほとんど停止時間を増やさずにメモリを解放します。

同様のコードをVB.NETで書いた場合、.NETのGCは世代0の回収を頻繁に行いますが、LOHに該当するサイズのオブジェクトが混ざると、世代2のフルGCが頻発し、数百ミリ秒から数秒の停止が発生する可能性があります。

実務におけるGC選定の指針

以上の点を踏まえると、メモリ管理の観点ではJavaが圧倒的に柔軟かつ高性能であると言わざるを得ません。
特に、以下のようなケースではJavaを選ぶべきです。

  • ヒープサイズが10GBを超える大規模アプリケーション
  • 許容遅延が50ミリ秒未満のリアルタイム性が求められるシステム
  • オブジェクトの生成頻度が極めて高く、短命オブジェクトが多いワークロード

逆に、VB.NETが適するのは、メモリ使用量が比較的少なく(2GB未満)、GC停止がそこまでクリティカルではない社内業務アプリや、.NETエコシステムとの親和性が優先されるケースです。
また、GCの挙動はランタイムバージョンによっても大きく変わるため、.NET 8以降ではサーバーGCの並行コンパクションが改善されていますが、それでもJavaのZGCには及びません。

最終的には、実際の負荷試験でGCログを取得し、停止時間とスループットを計測した上で判断することが不可欠です。
理論だけではなく、実データに基づく評価が、プロジェクト成功の鍵を握ります。

Webアプリ開発における実効性能:フレームワークと同時接続処理

Spring BootとASP.NET Coreを用いたWebアプリの負荷試験結果を比較した棒グラフ

Webアプリケーション開発において、言語のパフォーマンスはフレームワークの設計同時接続処理のアーキテクチャに大きく左右されます。
JavaとVB.NETは、それぞれが代表的なWebフレームワークを持ち、その実効性能は単なる言語処理速度以上に複雑な要因が絡み合います。
ここでは、実戦的なWeb開発の文脈で両言語を比較します。

フレームワークの特性とオーバーヘッド

JavaのWebフレームワークといえば、Spring Bootが事実上の標準です。
Spring Bootは、依存性注入(DI)、AOP(アスペクト指向プログラミング)、トランザクション管理などを統合した重厚なフレームワークですが、起動時のクラスパススキャンやプロキシ生成に大きなオーバーヘッドが発生します。
しかし、一度ウォームアップが完了すれば、非同期処理(WebFlux)リアクティブストリームを活用することで、高いスループットを実現できます。

  • Spring MVC(サーブレットベース)は、スレッドプールでリクエストを処理し、1リクエストあたりのメモリ消費は比較的安定しています
  • Spring WebFluxは、イベントループ型のNetty上で動作し、少数のスレッドで多数の同時接続を処理できるため、高負荷時にリソース効率が向上します
  • さらに、QuarkusやMicronautといったサブセット型フレームワークは、ビルド時にDIを解決し、反射を排除することで、起動速度とメモリフットプリントを大幅に削減しています

一方、VB.NETのWeb開発では、ASP.NET Core(旧ASP.NET MVC)が主流です。
ASP.NET Coreは、Kestrelという軽量Webサーバーを内蔵し、非同期I/O(async/await) を言語機能として深く統合しています。
これにより、スレッドブロッキングを起こさずに高同時接続を捌くことが可能です。
また、.NET 8では、Native AOT デプロイが強化され、起動速度とメモリ使用量がさらに改善されています。

同時接続処理モデルの比較

同時接続数が増えた際の挙動は、両言語で明確に異なります。
Javaの従来型サーブレットコンテナ(TomcatやJetty)は、リクエストごとにスレッドを割り当てるモデルを採用してきました。
このモデルは、スレッド数が増加するとコンテキストスイッチのオーバーヘッドが顕著になり、同時接続数が数千を超えると性能が頭打ちになります。
そこで、Spring WebFluxやVert.xのようなノンブロッキングモデルが登場しました。

フレームワーク 処理モデル スレッドモデル 推奨同時接続数 メモリ消費(アイドル時)
Spring Boot MVC ブロッキングI/O リクエスト=スレッド 〜1,000 〜300MB
Spring WebFlux ノンブロッキングI/O 固定(CPUコア数) 〜10,000 〜250MB
ASP.NET Core ノンブロッキングI/O(async) 動的スレッドプール 〜8,000 〜200MB

この表から分かるように、ノンブロッキングI/Oを採用しているフレームワークは、同時接続数に対するスケーラビリティが格段に優れています
ASP.NET Coreは、デフォルトでこのモデルを採用しており、追加のリアクティブライブラリを導入しなくても高負荷に耐えられる点が特徴です。
Javaで同水準の性能を得るには、WebFluxかVert.xを選択する必要があり、学習コストやデバッグの難易度が上がるトレードオフがあります。

データベースアクセスとの連携における性能差

Webアプリの実効性能は、データベースアクセス層にも依存します。
Javaでは、JPA(Hibernate)が主流ですが、このORMはN+1問題キャッシュ管理の複雑さがパフォーマンスを低下させる要因となります。
ただし、MyBatisのようなSQL中心のマッパーや、jOOQのような型安全クエリビルダーを選ぶことで、オーバーヘッドを削減できます。

VB.NETでは、Entity Framework Core(EF Core)が標準的なORMです。
EF Coreは、クエリのキャッシュや変更トラッキングの最適化が進んでおり、非同期クエリを標準サポートしています。
ただし、複雑なJOINや集計処理では、生成されるSQLが意図通りでない場合があり、その際はDapperなどのマイクロORMに切り替える選択肢もあります。

実際の負荷試験では、単純なCRUD処理では両者に大差がないものの、複数テーブルの結合やトランザクションが絡む複雑なビジネスロジックでは、JavaのJDBCベースのネイティブクエリが最も高速で、EF Coreの自動生成クエリはやや遅れる傾向があります。
ただし、この差はキャッシュ戦略やコネクションプール設定で埋められる範囲でもあります。

デプロイ環境とスケーリング戦略の影響

また、Webアプリのパフォーマンスはデプロイ環境にも依存します。
Javaアプリケーションは、JVMヒープサイズのチューニングが必須であり、メモリ割り当てを誤るとGCの頻発でスループットが激減します。
しかし、適切にチューニングされたJVMは、コンテナ環境(Kubernetes) でも安定した動作を示し、水平スケーリング時の状態共有(セッション管理など)も成熟したソリューションが揃っています。

VB.NET(.NET Core)もコンテナ対応が進んでおり、特にAlpine Linuxベースの軽量イメージを使うことで、Javaよりも少ないメモリフットプリントで動作させることが可能です。
ただし、Windows Serverベースの従来の.NET Frameworkから移行する場合は、IISの設定やアプリケーションプールの再起動ポリシーがパフォーマンスに影響するため、注意が必要です。

最終的に、Webアプリの実効性能を評価する際には、フレームワークの選定、非同期モデルの採用有無、データアクセス層の最適化、そしてインフラ構成を総合的に考慮する必要があります。
単純に「Javaが速い」「VB.NETが速い」ではなく、自分のアプリケーションのトラフィックパターンと開発リソースに最適な組み合わせを選択することが、成功への近道です。
特に、新規プロジェクトであれば、Spring Boot+WebFluxとASP.NET Coreの両方でプロトタイプを作成し、実際の負荷試験を実施することを強くお勧めします。

デスクトップアプリ開発での使い分け:UI応答性とリソース消費

Windows FormsとJavaFXで同じGUI処理を実行した際のCPU使用率とメモリ使用量の比較表

Webアプリケーションが隆盛を極める現代でも、デスクトップアプリケーションには高い応答性ローカルリソースの効率的な利用が求められる場面が数多く存在します。
JavaとVB.NETは、デスクトップ開発においても異なるアプローチと性能特性を持っており、プロジェクトの要件に応じた使い分けが重要です。
ここでは、UIスレッドモデル、メモリ消費、描画パフォーマンスの観点から比較していきます。

UIフレームワークの構造とイベントディスパッチ

Javaのデスクトップ開発では、SwingとJavaFXが主要な選択肢です。
SwingはAWTを基盤とした軽量コンポーネントであり、イベントディスパッチスレッド(EDT) という単一スレッドでUI更新をシリアライズ処理します。
このモデルは、マルチスレッドでのUIアクセスを禁止することで整合性を保つ反面、EDTで時間のかかる処理を行うとアプリケーション全体がフリーズするリスクがあります。

JavaFXは、より現代的なアーキテクチャを採用しており、レンダリングスレッドとアプリケーションスレッドが分離され、さらにPlatform.runLater()による非同期UI更新がサポートされています。
また、ハードウェアアクセラレーションを利用したグラフィックスパイプライン(Prism)により、アニメーションやメディア再生のパフォーマンスが向上しています。

一方、VB.NETのデスクトップ開発では、Windows FormsとWPF(Windows Presentation Foundation)が代表的です。
Windows Formsは、Win32 APIのラッパーとして動作し、シンプルなイベント駆動モデルを提供します。
こちらもUIスレッドは単一で、Control.Invokeを用いてスレッド間通信を行いますが、JavaのSwingと比較してネイティブウィンドウハンドルを持つため、描画のオーバーヘッドが小さいという利点があります。

WPFは、MVVMパターンデータバインディングを中心に設計された高度なフレームワークで、DirectXベースのレンダリングエンジンを搭載しています。
そのため、ベクターグラフィックスやアニメーション、3D表示においてJavaFXを凌ぐ表現力とスムーズさを発揮します。
ただし、その代償としてメモリ消費が大きく、複雑なバインディングはレイアウト再計算の負荷を増加させます。

メモリ消費と起動後のフットプリント

デスクトップアプリでは、ユーザーが体感する起動後のメモリ使用量が重要な指標です。
JavaのSwingアプリケーションは、JVMのベースメモリ(約30〜50MB)に加えて、各コンポーネントのオブジェクトヒープが積み上がります。
典型的な業務用Swingアプリでは、メモリ消費が150〜300MBになることが多く、これはユーザーから「重い」と感じられる要因です。

JavaFXは、グラフィックスパイプラインやメディアエンジンを持つため、さらにメモリを消費し、起動直後から200〜400MBを占有することが珍しくありません。
ただし、モジュールシステム(Java Platform Module System)を活用して不要なコンポーネントを除外すれば、ある程度削減可能です。

Windows Formsは、.NETランタイムの上で動作し、同じ規模のアプリでメモリ消費が80〜150MBに収まることが多く、Javaよりも軽量です。
WPFは、バインディングエンジンやビジュアルツリーの管理オーバーヘッドから、150〜300MBとJavaFXと同程度かやや多めになります。
ただし、WPFはGPUメモリも活用するため、システム全体のメモリ使用量はさらに増える点に留意が必要です。

フレームワーク 起動後メモリ(目安) 描画エンジン ハードウェアアクセラレーション 学習コスト
Swing 150〜250MB AWT(ソフトウェア) 限定的 低い
JavaFX 200〜400MB Prism(DirectX/OpenGL) 強力 中程度
Windows Forms 80〜150MB GDI+(ソフトウェア) なし 低い
WPF 150〜300MB DirectX(ハードウェア) 非常に強力 高い

UI応答性とバックグラウンド処理の連携

デスクトップアプリの体感速度は、UIスレッドをブロックせずにバックグラウンド処理を実行できるかどうかにかかっています。
Javaでは、SwingWorker(Swing)やTask(JavaFX)を用いて、バックグラウンドスレッドでの処理結果をUIに反映する仕組みが提供されています。
これらは、処理中もUIが応答し続けることを保証しますが、コールバックのネストが深くなるとコードが複雑化します。

VB.NETでは、BackgroundWorker(Windows Forms)やasync/await(WPF含む)が標準サポートされており、特にC#と共通の非同期パターンが利用できるため、コードの可読性が高い点が魅力です。
また、Dispatcher(WPF)を用いた優先順位付きUI更新も可能で、高負荷時でもアニメーションのスムーズさを維持しやすい設計になっています。

実務での選択基準

以上の特性を踏まえると、以下のような使い分けが合理的です。

  • シンプルな業務用フォームアプリ軽量なユーティリティには、Windows Formsが最適です。起動が速く、メモリも節約でき、VB.NETのラピッド開発能力と相性が良いからです
  • リッチなグラフィックスやアニメーションマルチプラットフォーム対応が必要な場合は、JavaFXを選ぶ価値があります。特に、Windows以外の環境(macOSやLinux)でも動作させたい場合、Javaは唯一の現実的な選択肢です
  • 企業内の大規模デスクトップアプリで、MVVMパターンモダンなUIデザインを重視するなら、WPFが強力です。ただし、開発チームにXAMLやバインディングのスキルが求められる点は注意が必要です

最後に、デスクトップアプリのパフォーマンスは、ネットワーク通信の有無データベースアクセス頻度にも大きく依存します。
UIフレームワークの選択だけではなく、非同期I/Oの適切な実装バックグラウンド処理の分割が体感速度を左右することを忘れないでください。
実際のプロジェクトでは、プロトタイプを作成し、タスクマネージャーやプロファイラでリソース消費を計測しながら、最適なフレームワークを選定することをお勧めします。

バッチ処理やバックグラウンドジョブではどちらを選ぶべきか

バッチ処理実行時のJavaとVB.NETの処理時間をジョブサイズ別に示した横棒グラフ

システム開発において、定期実行されるバッチ処理や非同期のバックグラウンドジョブは、基幹業務を支える重要なコンポーネントです。
これらの処理は、スループット安定性、そして運用のしやすさが重視されます。
JavaとVB.NETは、この領域でも異なる強みを持つため、ジョブの特性に応じた選択が求められます。

バッチ処理に求められる三つの要件

バッチ処理を評価する際には、以下の三つの観点が特に重要です。

  • 処理速度:大量のデータをいかに短時間で処理できるか。これは単なる言語実行速度だけでなく、I/Oやデータベースアクセスの効率にも依存します
  • リソース消費の予測性:メモリリークやGCの不安定性がなく、長期実行でもパフォーマンスが劣化しないこと
  • 障害耐性と再開性:途中で障害が発生した場合に、再実行や途中再開が容易であること

これらの要件に対して、JavaとVB.NETは異なるアプローチを提供します。

Javaのバッチ処理エコシステム

Javaには、Spring Batchという業界標準に近いバッチフレームワークが存在します。
Spring Batchは、チャンク指向処理というモデルを採用しており、大量データを一定件数ごとに区切って読み込み→処理→書き込みをトランザクション単位で実行します。
この設計により、以下のメリットが得られます。

  • 処理途中で障害が発生しても、最後にコミットされたチャンクから再開できる(再開可能性
  • 各チャンクが独立しているため、メモリ使用量が一定に保たれ、OutOfMemoryを防ぎやすい
  • ジョブの実行履歴やステータスをメタデータテーブルに保存し、運用監視が容易

また、Javaのマルチスレッド機能(ExecutorServiceや並列ストリーム)を活用すれば、チャンク単位での並列処理も実装可能です。
さらに、QuarkusやMicronautを用いれば、起動速度を改善したネイティブイメージでバッチを実行することもでき、短命バッチのデメリットを緩和できます。

加えて、JPAやMyBatisとの連携により、オブジェクトマッピングを活用した直感的なデータ操作が可能であり、複雑なビジネスルールをJavaの型安全なコードで記述できる点は大きな強みです。

VB.NETのバッチ処理アプローチ

VB.NETでは、専用のバッチフレームワークはJavaほど標準化されていませんが、Task Parallel Library(TPL)Channelを用いた並列処理、そしてMicrosoft.Extensions.Hostingによるバックグラウンドサービスとしての実装が一般的です。
特に、.NET 6以降で導入されたWorker Serviceテンプレートは、バッチ処理をデーモン化するのに適しています。

  • IHostedServiceBackgroundServiceを実装することで、アプリケーションのライフサイクルに沿ったジョブの開始・停止が制御できます
  • async/awaitによる非同期I/Oを活用すれば、データベースや外部APIへのアクセス中にスレッドをブロックせず、リソース効率が向上します
  • System.Threading.Channelsを用いたプロデューサー・コンシューマーパターンは、高スループットなデータパイプラインを構築するのに有効です

ただし、VB.NETにはバッチ固有の再開機能トランザクションチャンク管理が標準で提供されていないため、自前で実装する必要があります。
たとえば、処理済みレコードのIDをチェックポイントとして保存し、再実行時にそこから再開するロジックを組むといった対応が求められます。

実処理速度の比較:ファイルI/OとDBアクセス

バッチ処理の典型的なワークロードである「CSVファイルを読み込み、DBに一括挿入する」ケースを考えます。
JavaのBufferedReaderPreparedStatementのバッチインサートを用いた場合、1万件単位のコミットで最適なスループットが得られます。
また、JVMのメモリマップトファイル(MappedByteBuffer)を活用すれば、巨大ファイルの読み込みも高速化可能です。

VB.NETでは、StreamReaderSqlBulkCopy(SQL Server専用)またはNpgsqlBeginBinaryImport(PostgreSQL用)を組み合わせることで、Javaと同等以上の一括挿入速度を達成できるケースがあります。
特に、SqlBulkCopyは.NETのネイティブ最適化が効いており、数十万件単位のデータでも数十秒で転送可能です。

処理内容 Java(Spring Batch) VB.NET(Worker Service)
ファイル読み込み(1GB) 約8秒 約7秒
DB一括挿入(10万件) 約2.5秒(バッチサイズ1000) 約1.8秒(SqlBulkCopy)
複数テーブル更新(トランザクション) 約4.0秒 約5.2秒(EF Core経由)
障害時再開機能 標準実装あり 自作必須

この表から分かるように、単純な一括挿入ではVB.NETが速い一方、トランザクション整合性や再開性が求められる複雑なバッチではJavaのフレームワークが圧倒的に開発効率で勝ります

運用面での考慮点

バッチ処理は、実行モニタリングやログ管理も重要な要素です。
JavaのSpring Batchは、ジョブリポジトリを使って実行履歴をDBに記録し、REST APIやJMX経由でジョブの停止・再開ができます。
また、リトライスキップのポリシーもアノテーションで柔軟に設定可能です。

VB.NETでは、SerilogやNLogといったロギングライブラリと、Health Checksを組み合わせて監視するのが一般的ですが、標準でここまで統合された仕組みはありません。
そのため、運用規模が大きくなるほどJavaの整備されたエコシステムが有利になります。

最終的な選択基準

以上の比較から、バッチ処理では以下の方針が有効です。

  • 大規模な基幹系バッチで、障害復旧や監視の要件が厳しい場合は、Spring BatchをはじめとするJavaエコシステムを選ぶべきです。開発コストよりも運用コストと信頼性が優先される領域です
  • 小〜中規模の単発バッチデータ移行ツールなど、シンプルな入出力処理が中心で、開発スピードを重視するならVB.NET(特にSqlBulkCopyとの組み合わせ)が有力です。また、既存の.NET資産がある場合は、統一性の観点からもVB.NETが選ばれます

いずれにしても、バッチ処理の性能はSQLチューニングやI/Oバッファリングに依存する割合が大きいため、言語自体の速度差よりも、適切なライブラリ選択と実装パターンが最終的なパフォーマンスを決定づけることを強調しておきます。
実際のプロジェクトでは、まずはプロトタイプでボトルネックを特定し、その上で言語とフレームワークを確定するのが賢明です。

総合評価とプロジェクト別最適選択:パフォーマンスだけではない判断基準

システム要件、チームスキル、運用コストを軸にしたJavaとVB.NETの選択チャート

ここまで、起動速度、スループット、GC、Webアプリ、デスクトップ、バッチ処理という各領域でJavaとVB.NETのパフォーマンス特性を詳細に比較してきました。
しかし、実際のプロジェクトでは、生の処理速度だけが唯一の選択基準ではないということを改めて強調したいと思います。
ここでは、パフォーマンス以外の重要な判断軸を整理し、プロジェクトタイプ別に最適な選択を提案します。

パフォーマンス以外に考慮すべき五つの軸

言語やプラットフォームを選定する際には、以下の要素を総合的に評価する必要があります。

  • 開発生産性:コーディングのしやすさ、IDEのサポート、ライブラリの充実度、デバッグの容易さ。VB.NETはVisual Studioとの統合が非常に強力で、特にフォームデザイナやデータソースウィザードによるRAD開発が得意です。Javaは、IntelliJ IDEAやEclipseが高機能ですが、ビルドツール(Maven/Gradle)の設定や依存関係解決に一定の学習コストがかかります
  • 人材確保の容易さ:国内市場では、Javaエンジニアの絶対数が非常に多く、中途採用や外部委託がしやすいです。VB.NETは、レガシーシステムの保守需要はあるものの、新規参入者が減少傾向にあるため、長期的な人材確保ではJavaに軍配が上がります
  • エコシステムとサポート:Javaは、Spring、Hibernate、JUnit、Log4jなど、あらゆるレイヤーでデファクトスタンダードなライブラリが揃っており、トラブル時の情報量も豊富です。VB.NETは、Microsoftの公式サポートは手厚いものの、サードパーティ製のオープンソースライブラリはJavaほど多様ではありません
  • プラットフォーム依存性:Javaは「一度書けばどこでも動く」を掲げ、Windows/macOS/Linuxで同一バイナリが動作します。VB.NETは、.NET Core以降でクロスプラットフォーム対応が進みましたが、Windows FormsやWPFは依然としてWindows専用であり、LinuxでのGUI開発は事実上不可能です
  • 将来性と移行リスク:Javaは、現在もLTSリリースが継続され、大企業での採用が安定しています。VB.NETは、Microsoftの戦略的優先度がC#に移っているため、新機能の導入が遅れがちであり、将来的な言語サポートの縮退リスクを考慮する必要があります

プロジェクトタイプ別の最適選択マトリクス

これらの軸を踏まえ、代表的なプロジェクトタイプごとに推奨言語を整理します。

プロジェクトタイプ 推奨言語 主な理由
エンタープライズ基幹システム(長期稼働) Java ピーク性能、GCチューニング性、豊富なフレームワーク、人材プール
マイクロサービス/クラウドネイティブ Java(Quarkus/Spring Boot) コンテナ親和性、軽量イメージ化、リアクティブ対応
Windows専用業務アプリ(RAD重視) VB.NET(Windows Forms) 開発速度、低メモリフットプリント、Visual Studioとの統合
リッチなGUI/グラフィックスアプリ WPF(VB.NET可)またはJavaFX 表現力(WPF) vs クロスプラットフォーム(JavaFX)
単発データ移行/ETLバッチ VB.NET(SqlBulkCopy活用) 高速な一括挿入、簡潔なコード
大規模バッチ(障害耐性重視) Java(Spring Batch) チャンク指向、再開機能、監視仕組みの標準化
クロスプラットフォームデスクトップ Java(JavaFX) Windows/macOS/Linux全てで動作可能
教育用/プロトタイプ VB.NET 習得が容易で、即座に実行可能なコードが書ける

移行コストと既存資産の活用

また、既存システムとの整合性も無視できません。
もし自社に.NET Framework製の大量のライブラリやコンポーネントが存在するなら、VB.NETを継続することで移行コストをゼロにできます。
逆に、Java製のオープンソース資産を活用したい場合は、Javaを選択せざるを得ないでしょう。

さらに、最近ではGraalVMによるNative ImageがJavaの起動速度問題を解決しつつあり、VB.NETのR2Rと肩を並べる水準に達しています。
また、.NET 9以降ではさらなるAOT最適化が予告されており、両言語のパフォーマンス差は年々縮まっています。

最終的な判断フレームワーク

結論として、パフォーマンスだけで言語を選定するのは危険です。
私は以下の三ステップを推奨します。

  1. 非機能要件の優先順位を明確にする:スループット、レイテンシ、起動速度、メモリ制約のうち、何が最も厳しい制約かを定義します
  2. 既存の開発リソースと運用ノウハウを棚卸しする:チームのスキルセット、運用スクリプト、監視ツールとの親和性を評価します
  3. 両方でプロトタイプを作成し、実際の負荷試験を実施する:ベンチマークサイトの数値ではなく、自分のデータとネットワーク環境で計測することが唯一確実な方法です

最終的に、Javaは大規模で長寿命なシステムに、VB.NETはWindowsネイティブで短納期が求められる業務アプリに、という住み分けが現実的です。
両者には明確な優劣ではなく、適材適所が存在することを理解した上で、プロジェクトの成功に最も貢献する選択をしてください。
パフォーマンスはあくまで一要素であり、総合的なビジネス価値を最大化する視点が何より重要です。

コメント

タイトルとURLをコピーしました