Javaアプリケーションを起動する方法

はじめに:LZは初心者なので、たくさんの情報を見直してまとめました。何かおかしいところがあれば指摘してください。

まず、Javaで3つの概念を普及させる必要があります
。JDK:JRE、JDBなどを含むJava開発環境として簡単に理解できます。JRE

JVMを含むJavaアプリケーションの最小動作環境JVM:Java仮想マシン、アプリケーションの場合プログラムは、完全なハードウェア環境をシミュレートします。
つまり、JDK> JRE> JVMです。

では、Javaはどのようにしてクロスプラットフォームを実現するのでしょうか。
それがJVMを介していることは誰もが知っています。時間の見積もりによると、JVMは大量のCとアセンブリによって記述されています。Win64ビットシステムにはwin64用のJVMがあり、win32ビットシステムにはwin32用のJVMがあり、Linux64システムにはLinux64用のJVMがあります。つまり、各システムには特定のJVMがあり、特定のシステムのJVM実装は異なります。クロスプラットフォームを実現するために、オペレーティングシステムに直接リンクされていないJVMでJavaアプリケーションを実行します。

JVM実装の大部分は、32ビットのコンピューターハードウェアストレージを使用します。もちろん、独自のJVMを実装することで64ビットを実装することもできます。ほとんどのJVMは32ビットで整列されているため、スペースの基本的なタイプは次のとおりです。

byte 1
short 2 
char 2
int 4
long 8
float 8
object 4(对一个JavaObject对象的引用,2字节索引数据、2字节索引方法表)
returnAddress 4字节,jvm底层实现,开发者并不能使用

ブール型が表示されないようです。これは、jvmがブール型を整数として扱い、ブール型配列がバイト配列で表されるためです。

Javaがインタプリタ言語であることは誰もが知っています。
高級言語は、インタプリタ言語とコンパイル言語の2つのタイプに大別されます。

  • 通訳言語:通訳言語は実行時にコンパイルする必要はありませんが、コンピューターで認識されるためには翻訳する必要があります。つまり、プログラムを実行するたびに翻訳を行う必要があり、効率が低下します。
  • コンパイルされた言語:プログラムは実行前にコンパイルする必要があり、コンパイルの結果は機械語ファイルを直接生成します。再度実行する場合は、機械語ファイル(exeファイルなど)を直接実行してください。この場合、1回翻訳されます。そのため、効率が高くなります。

インタプリタ言語として、Javaは初期のCやC ++よりも低速でした。近年の開発により、Javaの速度は徐々に向上しており、最新バージョンの速度はC ++とほぼ同じです。

エントリプログラムのHelloWorldを記述した後、プログラムを実行するために、javacHelloWorld.javaとjavaHelloWorldの2つのコマンドを実行する必要があります。これらの2つのステップは正確に何をしましたか?
1つ目はjavacコマンドです。実行後、理解できないテキストの束が生成されます。これは実際にはHelloWorld.classファイルです。ファイルを解釈してみましょう。
HelloWorld.javaコードは次のとおりです。

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HelloWorld");
    }
}

生成される.classファイルは次のとおりです。

cafe babe 0000 0033 001c 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1207
0015 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000f 4865 6c6c 6f57
6f72 6c64 2e6a 6176 610c 0007 0008 0700
160c 0017 0018 0100 0a48 656c 6c6f 576f
726c 6407 0019 0c00 1a00 1b01 0010 6a61
7661 2f6c 616e 672f 4f62 6a65 6374 0100
106a 6176 612f 6c61 6e67 2f53 7973 7465
6d01 0003 6f75 7401 0015 4c6a 6176 612f
696f 2f50 7269 6e74 5374 7265 616d 3b01
0013 6a61 7661 2f69 6f2f 5072 696e 7453
7472 6561 6d01 0007 7072 696e 746c 6e01
0015 284c 6a61 7661 2f6c 616e 672f 5374
7269 6e67 3b29 5600 2100 0500 0600 0000
0000 0200 0100 0700 0800 0100 0900 0000
1d00 0100 0100 0000 052a b700 01b1 0000
0001 000a 0000 0006 0001 0000 0001 0009
000b 000c 0001 0009 0000 0025 0002 0001
0000 0009 b200 0212 03b6 0004 b100 0000
0100 0a00 0000 0a00 0200 0000 0300 0800
0400 0100 0d00 0000 0200 0e

これらの内容が16進数であることが簡単にわかります。
ここで最初にクラスファイルの構造を投稿します

ClassFile {
    u4 magic;
    u2 minjor_version;
    u2 major_version;
    u2 constant pool count;
    cp info constant pool[constant pool count - 1];
    u2 access flags;
    u2 this Class;
    u2 super Class;
    u2 interfaces count;
    u2 interfaces[interfaces count];
    u2 fields count;
    fields info fields[fiedls count];
    u2 method count;
    method into methods[methods count];
    u2 attributes count;
    attribute info attributes[attribute count];
}

それらを1つずつ比較できます。
クラスファイルは16進数であるため、2桁ごとに1バイトを表します。上記のu2は2バイトを表します。
magic:Magic、jvm仕様では、クラスファイルは0xCAFEBABEで始まる必要があると規定されています。これは、ファイルをjvmによって実行されるファイルとして識別するために使用されます。ただし、これは最も予備的なテストにすぎません。JVMはサンドボックスモデルも提供します。これについては後で説明します。(リーダーは、生成されたクラスのマジックを許可なく変更し、javaコマンドを使用できます。効果を確認できます)
minjor_version:マイナーバージョン番号。major_versionを実行しているクラスファイルのマイナーjvmバージョン番号を宣言するために使用されます
。メジャーバージョン番号、
定数プールカウントを実行しているクラスファイルのメインjvmバージョン番号を宣言するために使用されます:アプリケーションによって使用される定数プールの数を表します。int、String、doubleなどがあります。infoconstantpool
[constant pool count-1]:各定数プールの特定の情報を表します。各定数プールはタイプを表し、各定数プールにはそのタイプのライトを格納するための配列があります。
アクセスフラグ:ファイルがインターフェイスであるかクラスであるか、および修飾子がパブリック、プライベート、または保護されているかどうかを定義します。
このクラス:jvm内のこのクラスの定数プールエントリのインデックスを表します。
スーパークラス:このクラスの親クラス
interfacescountおよびinterfaces [interfaces count]:インターフェイスの数およびinterface固有の情報
フィールドcountおよびinfoフィールド[fiedlscount]:このクラスのすべての属性および属性固有の情報
メソッド数とメソッド[メソッド数]:このクラスのすべてのメソッドとメソッド固有の情報
属性数:および情報属性[属性数]:LZは理解できないだけであり、これら2つのフィールドの意味を理解できません。

クラスファイルの解釈は終了しましたが、クラスファイルはどのように生成されますか?ここではOpenjdkを使用する必要があります。読者はopenjdkとjdkの違いを理解できます。javacコマンドはcom.sun.tools.javac.main.JavaCompilerファイルに存在しますが、LZがそれを理解しない場合、私はそれを研究しません。
ここで話したいのは、Javaファイルをコンパイルしてクラスファイルを生成するプロセスです。
字句解析:素人の言葉で言えば、各単語のスペルが正しいかどうかを確認することです。
構文解析:一般的には、単語がコンテキストに準拠しているかどうかを確認します。たとえば、最終クラスを継承できない、最終変数を再度割り当てることができないなどです。
コードの生成:不完全なコードを補足します。たとえば、次のパラメータを記述しません。このクラス、クラスを完全修飾名(com / example / HelloWorldなど)として入力します。
もちろん、特定のプロセスはコンパイルの原則の説明に依存します。ここでは概要のみを示します。

上記のコンパイルプロセスが完了し、ターゲットファイルクラスファイルも生成されました。次に、それを実行する必要があります。
単純なコマンドjava <クラス名>は何をしますか?
まず、アプリケーションのjvmインスタンスを開始します。これは、すべてのJavaアプリケーションにjvmインスタンスがあることを意味します。
コードが
jvmにロードされた後、クラスファイルのコンテンツがロードされます。クラスは独自の名前空間に格納されます。クラス間のアクセスには名前空間を介してのみアクセスできますが、すべてのクラスは同じメモリ空間にあります。 。すべてのクラスは、より効率的に相互に参照します。
追加することの1つは、アプリケーションがmain関数によって実行されることです。すべてのクラスが最初にロードされるわけではありませんが、どのクラスを使用する必要があり、jvmはどのクラスをロードします。クラスをロードするモデルメカニズムについては、完全な委任+親の委任メカニズムを使用して実装されます。
まず、どのようなローダーがあるのか​​を理解する必要がありますか?

  • クラスローダーを起動します
  • 標準の拡張クラスローダー
  • パスクラスローダー
  • カスタムローダー

上から順に、これは親子構造です。つまり、スタートアップクラスローダーは、標準の拡張クラスローダーの父です。ただし、ここでの父は、クラスの継承関係と同等ではありません。
スタートアップクラスローダーはCまたはC ++で記述されています。親クラスがない場合は、rt.jar(rtはオペレーティング環境を意味します)の
標準拡張クラスローダーをロードします。これは、extをロードするために使用されます。
ディレクトリの下のクラスパスクラスローダー、jarパッケージと作成したクラスをロードし
、Javaアプリケーションの実行後、クラスローダーをカスタマイズして、下にないクラスのコンパイルとロードを続行することもできます。プロジェクト。(これについては、以下で詳しく説明します)。
JVMは親委任メカニズムを使用することを述べましたが、これには大きな利点があります。
java.lang.Stringクラスを作成したとします。常識によれば、このクラスはjdkソースコードにすでに存在し、Javaアプリケーションが次の場合にクラス(スタートアップクラスローダーによってロードされる)がロードされることを知っておく必要があります。開始しました。java.lang.Stringクラスを自分で作成したので、それを使用するときに、それをjvmにロードできれば、整数、ブールなど、同じパッケージ内のすべてのクラスにアクセスできます。特別な変数にアクセスします。しかし、それはとても美しかったです。JVMはStringクラスが使用されることを認識するため、パスクラスローダーは最初に標準拡張クラスローダーにロードを委託し、標準拡張クラスローダーはそれがないことを検出し、スタートアップクラスローダーにロードを委託します。ただし、スタートアップクラスローダーはすでに同じ完全修飾名でjava.lang.Stringクラスをロードしており、結果は標準拡張クラスローダーに返され、標準拡張クラスローダーは結果をパスに返します。クラスローダー。パスクラスローダーは、スタートアップクラスローダーによってロードされたStringクラスを受け取るため、自分で作成したStringクラスはロードされません親委任メカニズムを使用すると、嫌なコードがjvmに影響を与えるのを防ぐことができます。

問題は、java.lang.Helloを宣言した場合、java.lang.String(jdkに付属)の特別な変数にアクセスできるかどうかです。結果は絶対に不可能です。jvm仕様は、同じローダーによってロードされたクラスのみが特別な権限を持つことができることを認識しています。また、java.lang.Helloはパスクラスローダーによってロードされ、java.lang.Stringはスタートアップクラスローダーによってロードされるため、2つのクラスは特別なアクセス許可を持つ変数とメソッドにアクセスできません。

上記は親の委託に還元され、次は完全な委託です。まず、Javaアプリケーションが最初にすべてのクラスをロードするわけではなく、どのクラスを使用する必要があり、どのクラスがjvmによってロードされるかを明確にする必要があります。たとえば、Hello1クラスがHelloクラスで使用されている場合、HelloローダークラスはHello1クラスをロードします。Helloクラスがパスクラスローダーによってロードされると仮定すると、パスクラスローダーは最初にHello1クラスをロードします。これが全体的な委任メカニズムです。

コード検証
生成されたクラスファイルは人為的に変更される可能性があるため、jvmはクラスファイルを検証する必要があります。

  • まず、クラスファイル構造が要件を満たしているかどうかを確認します。例:魔法のoxCAFEBABEから始めます。メジャーバージョン番号とマイナーバージョン番号の情報から、jvmがこのクラスを実行できるかどうかを確認します。ClassFileの構造に準拠しているかどうか(コードは上記で投稿および説明されています)。
  • メソッドの戻り値の型と関数の宣言に一貫性があるかどうか、関数のパラメーターが一致するかどうかなど、セマンティクスが正しいかどうかを確認します。
  • バイトコードの検証、バイトストリームからの分析、これらのバイトストリームはクラスのメソッドを表します。
  • シンボル参照の検証、使用する必要のあるクラスのスキャン、完全な委任による必要なクラスのロード、シンボル参照の直接参照への置き換えなど。

コードの実行
次に、コードの実行を開始し、クラスファイルの内容をJava命令セットに変換します。これはJavaプログラムのアセンブリ言語に相当します。
これまでのところ、コンパイルと実行のプロセスはおおよそ上記のとおりです。ご不明な点がございましたら、訂正してください。
最後に、プログラムの実行が開始されます。プログラムが実行されるとすぐに、デーモンスレッド-GCリサイクルスレッドが開始されます(Thread.setDaemonを呼び出して、カスタムスレッドをデーモンスレッドとして設定することもできます)。
デーモンスレッド-優先度が非常に低く、ユーザースレッドと本質的な違いがなく、常にjvmバックグラウンドで実行されているスレッド。関数がSystem.exit(0)を呼び出すか、すべてのユーザースレッドの実行が終了すると、すべてのデーモンスレッドがすぐに終了します。
ユーザースレッド-優先度の高いスレッド。すべてのユーザースレッドが実行されるまで、アプリケーション全体が終了と見なされます。ただし、System.exit(0)メソッドはjvmを終了するためのものであり、これによりアプリケーション全体が終了します。

GCリサイクルスレッドの優先度は非常に低く、System.gc()を人為的に呼び出すと、GCリサイクルスレッドにガベージをリサイクルするように通知するだけですがいつリサイクルされるかは定かではありません。
リサイクルアルゴリズムには、マークコピー、マークスイープ、その他のアルゴリズムが含まれます。知識が限られているため、詳細には触れません。
Jvmは、ルートルートノードを介してすべてのオブジェクトの検索を開始し、オブジェクトに到達できない場合は、オブジェクトが占有していたメモリスペースを再利用します。

Javaアプリケーションはスタックベースですが、なぜそう言うのですか?まず、すべてのスレッドで共有されるリソースは、ヒープやファイルなどです。GCによって再利用されるメインメモリスペースはヒープです。スレッドに固有のリソースはどうですか?スタック、優先度、プログラムカウンタなどです。
JVMでは、スタックはスタックフレームで構成され、スレッドは複数のメソッドで構成される場合があり、各メソッドは独自のスタックフレームで構成されます。各スタックフレームは、一意のローカル変数領域、オペランドスタック、演算子スタック(結局のところ、Javaには独自の命令セットがあります)、データ領域などで構成されています。jvmスタックフレームにもサイズ規制があります。再帰を終了できない場合、java.lang.StackOverflowError例外がスローされます。
プログラムカウンタは、スレッドが次に実行する命令を示すために使用されます。次の命令がローカルメソッドを指している場合、値は未定義になります。
スレッド間の同期と非同期を実現するために、スレッドの優先度の定義に依存しないでください。優先度は参照値のみです。jvmによって実行されるスレッドは、複数の値を参照します。

特別なクラス-クラスクラス
Objectクラスは非常に特別だと思うかもしれませが、もちろん、Objectクラスはすべてのクラスの親クラスです。しかし、ここで話したいのはクラスクラスです。クラスクラスは、クラスを維持するためのクラスとして理解できます。jvmがクラスファイルをロードした後、クラスの静的変数クラスを生成します。クラスオブジェクトを介して、クラスのすべての属性、メソッド、インターフェイス、ローダーなどを取得できます。

上記のように、クラスローダーローダーをカスタマイズできます。これは、Javaフレームワークによって実装されるメソッドの1つでもあります。たとえば、Webサーバーを実装する場合、Javaアプリケーションは、ユーザーが持っているクラスの数、クラスのファイルの場所、およびクラスの名前を確実に認識しません。ただし、最初にControllerクラスを定義して、すべてのリクエストをインターセプトし、doGetメソッドとdoPostメソッドを定義できます。Webサーバーが起動するとすぐに、すべての.javaファイルを再帰的に検索して.classファイルにコンパイルし(JavaCompilerクラスを使用して実装)、次に.classファイルを仮想マシンにロードします(ClassLoaderサブクラスを使用して実装)。ブラウザが127.0.0.1/ HelloWorld / indexにアクセスした場合は、HelloWorldディレクトリでインデックス文字列と結合されたControllerサブクラスを見つけて、doGetメソッドまたはdoPostメソッドを呼び出します。また、SpringMVCのアイデアを検討し、アノテーションをカスタマイズし、リフレクションメカニズムを通じて、対応するコントローラーサブクラスをより便利かつ迅速に見つけることができます。もちろん、これは大まかな考えであり、実現するのはまだ非常に困難です。

LZは初心者であることを繰り返し述べたいので、要約するために多くの情報を確認しました。何か問題がある場合は、
PSを指摘してください。ロード後にクラスを作成するのを忘れたようです。jvmはクラスの初期化手順を実行します。これ以上は実行しません。さよなら。

おすすめ

転載: blog.csdn.net/new_Aiden/article/details/52836643