JVMのロード順序
javacコンパイラ->バイトコード->クラスローダー->メモリ。クラスのロード手順には、次の3つの手順が必要です。
1.ロード(ロード)
- クラスの完全修飾名を使用して、このクラスを定義するバイナリバイトストリームを取得します。
- このバイトストリームによって表される静的ストレージ構造を、メソッド領域のランタイムデータ構造に変換します。
- このクラスを表すjava.lang.Classオブジェクトは、メソッド領域のこれらのデータへのアクセスエントリとしてJavaヒープに生成されます。
- ロードフェーズが完了すると、バイナリバイトストリームは、仮想マシンで必要な形式に従って正方形の領域に格納されます。
2.リンク(関連付け)
- 検証(チェック):ロードされたクラスが解析標準を満たしているかどうかをチェックするために使用されます
- ファイル形式の検証:バイトストリームがクラスファイル形式の仕様に準拠しており、仮想マシンの現在のバージョンで処理できることを確認します。
- メタデータ検証:記述された情報がJava言語仕様の要件を満たしていることを確認するための、バイトコードによって記述された情報のセマンティック分析
- バイトコード検証:この段階は、主にデータフローと制御フローを分析して、検証されたクラスのメソッドが実行時に仮想マシンのセキュリティを危険にさらす動作を実行しないことを確認することです。
- シンボル参照の検証:このフェーズは、仮想マシンがシンボル参照を直接参照に変換するときに発生し(解析フェーズ)、主にクラス自体以外の情報の一致をチェックします。目的は、解析アクションを正常に実行できるようにすることです。
- 準備(準備):クラスの静的変数にデフォルト値を割り当てます。通常、基本型は0で、参照型はnullです。
- 変数にメモリを正式に割り当て、初期値を設定します。これらのメモリはメソッド領域に割り当てられます。ここでの変数にはクラススカラーのみが含まれ、インスタンス変数は含まれません。
- 解決策:クラスの定数プール内のアドレスシンボルを直接メモリアドレスとアクセス可能なメモリアドレスに変換します(実際の初期化割り当てはなく、多くの人がここで混乱することに慣れています)
- シンボル参照:シンボル参照は、参照されるターゲットを一連のシンボルで記述します。シンボルは、使用時にターゲットを明確に配置できる限り、任意の形式のリテラルにすることができます。シンボリック参照は、仮想マシンによって実装されるメモリレイアウトとは関係がなく、参照されるターゲットが必ずしもメモリにロードされているとは限りません。
- 直接参照:直接参照は、ターゲットを直接指すポインター、相対オフセット、またはターゲットを間接的に見つけることができるハンドルにすることができます。直接飲酒はメモリレイアウトに関連しています。
- クラスまたはインターフェイスの解決
- フィールド分析
- クラスメソッド分析
- インターフェイスメソッド分析
3. Initalizing(初期化):静的メンバ変数に初期値を割り当て、クラスコンストラクタ<clinit>メソッドを呼び出す工程、次いで実際の初期割り当てが始まります。(2.2と2.2の間の命令の再配置も、セミインスタンス化の理由です。プラグインBinEd-Binaryプラグインを使用して、コンパイルされたバイトコードファイルを表示できます。これは、シングルトンモードでのレイジーロードがvolatileを使用する主な理由でもあります)
クラスローダー
1. Bootstrapクラスローダー(スタートアップクラスローダー)は最上位のクラスローダーです。ロードパスは、C ++で実装された< JAVA_HOME > \ libディレクトリ内のjarファイル/charset.jarおよびその他のコアクラスです。
2.拡張クラスローダー(拡張クラスローダー)は、< JAVA_HOME > \ lib \ extディレクトリにある、または-Djava.ext.dirsで指定されたjarファイルをロードします。
3. Appクラスローダー(システムクラスローダー)は、コマンドjavaのclasspathまたはjava.class.pathシステムプロパティまたはCLASSPATHオペレーティングシステムプロパティで指定されたJARクラスパッケージとクラスパスをロードする役割を果たします。自己がない場合アプリケーションで独自のクラスローダーを定義します。通常の状況では、これがプログラムのデフォルトのクラスローダーです。
4.カスタムは(カスタムクラスローダー)であり、ユーザーが実装します。
親の委任のプロセスの図
親の委任メカニズム。
まず、コードを使用して、親の委任の存在を示します。(ご自身で確認できます)
ここに質問を追加します:
親はどのように指定されますか?
ソースコードはsuper(parent)で指定されます。
また、指定されていない場合、デフォルトはAppClassLoader @ 18b4aac2です。自分でコードを試してみることができます。ソース・コードのデフォルト値が見つかりませんでした。
public class T004_ParentAndChild {
public static void main(String[] args) {
System.out.println(T004_ParentAndChild.class.getClassLoader());
System.out.println(T004_ParentAndChild.class.getClassLoader().getClass().getClassLoader());
System.out.println(T004_ParentAndChild.class.getClassLoader().getParent());
System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent());
//System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent().getParent());
}
}
打印出来是这样:
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@12bb4df8
null
(ClassLoaderのロードプロセスはテンプレートメソッドのパターンデザインパターンを使用します)
JVMは、親委任メカニズムを使用して、クラスがロードされているかどうかを確認するために、オンデマンドで動的にロードされます(図の1> 2> 3の順序)。クラスローダーがクラスをロードしていない場合は、トップになります。 -down(4> 5)> 6)クラスが見つかり、メモリにロードされるまで、ロードされたクラスを再度探します。
親委任メカニズムを使用する利点は、安全性、外部にロードされたクラスと内部のクラスローダーチェーン間の競合、および悪意のある損傷を回避することです。同じ名前のクラスがロードされた場合、JVMは最初にそのようなクラスにすでにロードされている場合、クラスは再度ロードされません。
ClassLoaderのソースコードを見ることができます。name
パラメーターはクラスの名前であり、実際の呼び出しはloadClass(String name)です。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
调用loadClass(文字列名、ブール値解決)
実行シーケンスは上図のようになります。
- 最初にfindLoadedClass(name)を呼び出して、同じクラスが以前にロードされているかどうかを確認します。どこにあるかについてはどうでしょうか。最初にメモリ内のハッシュセットテーブルに移動して検索すると聞きました(ソースコードが見つかりませんでした。見つかった場合は、それについて話すことができます)。
- 見つからない場合は、parent.loadClass();を呼び出します。これが反復です。見つかるまで。
- 見つからない場合は、findClass(name);メソッドがClassNotFoundException(name)をスローします。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
JVMが親の委任を使用するのはなぜですか?
- 安全のために(最も重要)
- 親の委任が必要ない場合は、カスタムローダーをjava.lang.Stringにロードし、パッケージ化して顧客に送信すると、組み込みの文字列ライブラリが上書きされます。通常、パスワードは文字列ストレージに保存されます。このとき、カスタム文字列クラスでオブジェクトのデータベースにメールボックスを送信または保存するビジネスラインを追加すると、カスタム文字列を使用するのと同じで、パスワードを簡単に取得できます。
- 効率のために
拡張:
1.クラスローダーの暗号化
クラスローダーがロードされるときに暗号化できます。コードは次のとおりです。
ここで使用されているものも暗号化されている可能性があります。
public class T007_MSBClassLoaderWithEncription extends ClassLoader {
public static int seed = 0B10110110;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("c:/test/", name.replace('.', '/').concat(".msbclass"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b ^ seed);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();//可以写的更加严谨
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}
public static void main(String[] args) throws Exception {
encFile("com.mashibing.jvm.hello");
ClassLoader l = new T007_MSBClassLoaderWithEncription();
Class clazz = l.loadClass("com.mashibing.jvm.Hello");
Hello h = (Hello)clazz.newInstance();
h.m();
System.out.println(l.getClass().getClassLoader());
System.out.println(l.getParent());
}
/**
* 用亦或加密
* @param name
* @throws Exception
*/
private static void encFile(String name) throws Exception {
File f = new File("c:/test/", name.replace('.', '/').concat(".class"));
FileInputStream fis = new FileInputStream(f);
FileOutputStream fos = new FileOutputStream(new File("c:/test/", name.replaceAll(".", "/").concat(".msbclass")));
int b = 0;
while((b = fis.read()) != -1) {
// 亦或一个数,再亦或的话就解密了
fos.write(b ^ seed);
}
fis.close();
fos.close();
}
}
2.クラスローダーはいつ初期化を開始しますか?
JVM仕様では、いつロードするかは指定されていません。ただし、いつ初期化する必要があるかを厳密に規定しています。
クラスのロードとは、初期化中にすべてのクラスをロードすることではなく、使用時にクラスをロードすることです。遅延読み込みが発生します。遅延読み込みには5つの状況があります。
-
新しいオブジェクト、静的変数の取得とアクセス、を除いて最終変数へのアクセスを忘れないでください。
-
java.lang.reflectがクラスへのリフレクション呼び出しを行うとき。
-
サブクラスを初期化するとき、親クラスが最初に初期化されます。
-
仮想マシンの起動時に、実行するメインクラスを初期化する必要があります。
-
動的言語サポートjava.lang.invoke.MethodHandleがREF_getstaticREF_putstatic REF_invokestaticのメソッドハンドルに解決されるとき、クラスを初期化する必要があります。
3.親の委任メカニズムを破る方法は?
上記のソースコードから、ClassLoaderのloadClass()メソッドがオーバーライドされている限り、親クラスのloadClass()がloadClassメソッドで呼び出されるのではなく、独自のクラスを直接ロードするために呼び出されるため、親が委任メカニズムが壊れている可能性があります。
4.親の委任メカニズムはいつ壊れますか?
- JDK1.2バージョンより前は、カスタムカスタムClassLoaderはloadClass()をオーバーライドする必要があります。
- ThreadContextClassLoaderは、thread.setContextClassLoaderで指定された基本的なクラス呼び出し実装クラスコードを実装できます。
- ホットスタート。osgi tomcatには、クラスローダーと呼ばれる独自のモジュールがあります(同じクラスライブラリの異なるバージョンをロードできます)
- tomcateのホットデプロイメントは、親委任メカニズムを破壊します。クラスファイルの変更は、コンテキストにすぐに同期できます。実際、本質は、クラスを1回リロードすることです。興味がある場合は、tomcatの実装原理について学ぶことができます。 loadClass()メソッドを記述しました。
loadClass()コードを書き直します。
public class T012_ClassReloading2 {
private static class MyLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
File f = new File("C:/work/ijprojects/JVM/out/production/JVM/" + name.replace(".", "/").concat(".class"));
if(!f.exists()) return super.loadClass(name);
try {
InputStream is = new FileInputStream(f);
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
public static void main(String[] args) throws Exception {
MyLoader m = new MyLoader();
Class clazz = m.loadClass("com.mashibing.jvm.Hello");
m = new MyLoader();
Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");
System.out.println(clazz == clazzNew);
}
}
5.JVMの混合モード
デフォルトでは、これは混合モードです(混合インタープリター+ホットコードコンパイル)。
Javaは解釈され、実行されます。クラスファイルがメモリに格納された後、Javaインタープリター(バイトコードインタープリター)を介して実行されます。
JIT(Just In-Timeコンパイラ):一部のコードは、実行のためにローカル形式のコードにコンパイルされます。
したがって、Javaは単にインタプリタ言語またはコンパイル言語であるとは言えません。
1. JITはいつローカルコードのコンパイルに使用されますか?
コードを書いたところ、最初はインタプリタで実行されていましたが、実行中にあるコードが非常に頻繁に実行され(1秒間に数十万回実行された)、JVMはこのコードをローカルコードにコンパイルします(C言語を使用してローカル* .exeファイルをコンパイルするのと同様)。コードを再度実行すると、インタープリターは実行に使用されないため、効率が向上します。
2.実行効率を向上させるために、ローカルコードを直接コンパイルしてみませんか?
- Javaインタープリターの実行効率は実際には非常に高く、一部のコードの実行効率は必ずしもネイティブコードの実行によって失われるわけではありません。
- 実行されたコードが多くのクラスライブラリを参照している場合、実行時間は非常に長くなります。
3つ目は、パラメーターを使用してモードを変更することです。
- -Xmixed:デフォルトは混合モードで、起動速度が速く、ホットコードが検出されてコンパイルされます。
- -Xint:通訳モードを使用し、すばやく起動し、実行速度を少し遅くします。
- -Xcomp:純粋なコンパイルモード、高速実行、低速起動を使用します(ライブラリが多数ある場合)
コード検証:
構成の編集-> VMオプション
- ミックスモード:
- パラメータを変更せず、デフォルト設定を使用してください
- 実行時間:約2700
- 説明モード:
- 将編集構成-> VMオプション->-Xint
- 実行時間:実行時間が長すぎて、0,19000の1サイクルを引いたもの
- コンパイルモード:
- 将編集構成-> VMオプション->-Xcomp
- 実行時間:2600
public class T009_WayToRun {
public static void main(String[] args) {
for(int i=0; i<10_0000; i++)
m();
long start = System.currentTimeMillis();
for(int i=0; i<10_0000; i++) {
m();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void m() {
for(long i=0; i<10_0000L; i++) {
long j = i%3;
}
}
}
次の記事:[JVMの詳細な理解] 3.CPUストレージ+ MESI + CPU疑似共有+ CPU障害の問題とコードのデモンストレーション[インタビューが不可欠]