[[361294]] この記事はWeChatの公開アカウント「bugstack」から転載したもので、著者はXiao Fu Geです。この記事を転載する場合は、bugstack の公開アカウントにご連絡ください。 目次 - 1. はじめに
- 2. 面接の質問
- 3. クラスロードプロセスの説明
- 4. 読み込むコードを書く
- 5. バイトコードファイルの解析
- 1. バイトコードを抽出する
- 2. マジックナンバーを分析して検証する
- 3. バージョン番号情報の解析
- 4. すべてのコンテンツを分析して比較する
- VI.結論
- 7. シリーズのおすすめ
1. はじめに 勉強したいけど、どこから始めればいいか分からない? 新しい知識の学習をどこから始めればよいかわからない場合、最も効果的な方法は、知識構造のコンテキスト情報を整理し、完全な思考ガイドを要約することです。次のステップは、マインドマップの知識構造に従って、対応する知識ポイントを一つずつ学習し、要約して記録することです。 JVM の学習と同様に、非常に多くのコンテンツが含まれており、巨大な知識体系でもあると言えます。たとえば、クラスのロード、ローダー、ライフサイクル、パフォーマンスの最適化、チューニング パラメーター、チューニング ツール、最適化スキーム、メモリ領域、仮想マシン スタック、ダイレクト メモリ、メモリ オーバーフロー、メタスペース、ガベージ コレクション、到達可能性分析、マーク アンド スイープ、リサイクル プロセスなどです。よく考えずに飛び込んで、場当たり的に試してみると、学習に対する恐怖心が簡単に生まれてしまいます。 図 24-1 は JVM 知識フレームワークを示しています。この構造の各コンテンツについては後ほど説明します。 図24-1 JVM知識フレームワーク 2. 面接の質問 ご連絡ありがとうございます!多くの知識はただ暗記されるだけで、実践することができません。学ぶのは難しいです! 「謝飛機」: 兄さん、JVM に関する質問を 2 つしてください。自分でできるか確認してみます! インタビュアー:えっ?えーっと!質問が多すぎませんか? 「謝非機」:大丈夫、大丈夫!それはあなた次第です! インタビュアー: ああ、JVM ロード プロセスの手順は何ですか? 「ありがとう飛行機」:バラバラ、ロード、検証、準備、解析、初期化、使用、アンロード! インタビュアー:よく覚えていらっしゃいましたね!一度も操作したことがないのではないでしょうか。ロード時に、JVM 仕様では、定数プールをどの番号から解析するか、データ型はどのように定義されるかが規定されています。 u1、u2、u4とは何ですか? 「谢飞机」: ちくしょう!忘れて、何を見るべきか教えてください! 3. クラスロードプロセスの説明 図24-2 JVMクラスのロードプロセス JVM クラスのロード プロセスは、ロード、リンク、初期化、使用、アンロードの 4 つの段階に分かれています。リンク段階には、検証、準備、解析も含まれます。 - 「ロード」: Java 仮想マシン仕様では、クラス ファイルの形式については厳格なルールが定められていますが、クラス ファイルをロードする場所については自由度が非常に高くなっています。 Java 仮想マシン実装は、ファイル システムから読み取り、JAR (または ZIP) 圧縮アーカイブからクラス ファイルを抽出できます。さらに、クラス ファイルはネットワークからダウンロードしたり、データベースからロードしたり、実行時に直接生成したりすることもできます。
- 「リンク」:3つのステージがあります。
- 検証し、ロードされたクラスの正確性を確認し、バイト ストリームがマジック ナンバー 0xCAFEBABE やバージョン番号などのクラス ファイル仕様に準拠しているかどうかを確認します。
- クラスの静的変数を準備し、メモリを割り当て、変数の初期値などを設定します。
- 解析: 解析には、ConstantPool 構造と AttributeInfo インターフェイスを含む定数プール データと属性テーブル情報の解析が含まれます。
- 初期化:クラスのロード後の最後のステップは初期化であり、定数値をマークするフィールドに値を割り当てて実行することです。方法のプロセス。 JVM 仮想マシンはロックを使用して、clinit が 1 回だけ実行されるようにします。
- 「使用」: プログラム コード実行の使用フェーズ。
- 「アンインストール」: プログラムコードの終了、例外、終了など。
4. 読み込むコードを書く JVM を習得するのが難しい理由は、主に実装が難しいためです。仮想マシンは C++ で記述されており、多くの Java プログラマーは単にその読み方がわからなかったり、理解できなかったりします。そうすると、実際にどのようにロードされるのか、ロード中に何が行われるのかを体験する方法はありません。コードを見たときだけ、それを学んだような気がします! したがって、クラスの読み込みプロセスである JVM 仮想マシン用のコードを手動で記述する必要があります。 Java 仮想マシンの一部の機能を Java コードを通じて実装すると、Java コードを開発するプログラマーが仮想マシンの実行プロセスを理解しやすくなります。 1. ケースプロジェクト - インタビュー-24
- ├── pom.xml
- └── 出典
- └── メイン
- │ └── ジャワ
- │ └── org.itstack.interview.jvm
- │ ├── クラスパス
- │ │ ├── 実装
- │ │ │ §── CompositeEntry.java
- │ │ │ §── DirEntry.java
- │ │ │ §── WildcardEntry.java
- │ │ │ └─ ZipEntry.java
- │ │ §── クラスパス.java
- │ │ └── エントリ.java
- │ ├── Cmd.java
- │ └── メイン.java
- └── テスト
- └── ジャワ
- └── org.itstack.interview.jvm.test
- └── HelloWorld.java
前述のように、プロジェクト構造は、Java コードを使用して、JVM 仮想マシン仕様に従って JVM でのクラス ファイルの読み込みを実装することです。もちろん、解析部分のコードが非常に大きいため、この部分には解析は含まれません。まず、.class ファイルを読み込んで読み込んでみましょう。 2. コードの説明 2.1 クラスパスインターフェースの定義(エントリ) - パブリックインターフェースエントリ{
-
- byte[] readClass(String className) は IOException をスローします。
-
- 静的エントリ作成(文字列パス) {
- //File.pathSeparator;パス区切り文字 (win\linux)
- if (path. contains (File.pathSeparator)) {
- 新しい CompositeEntry(path)を返します。
- }
- パスが「*」で終わる場合
- 新しい WildcardEntry(path)を返します。
- }
- path.endsWith( ".jar" ) || path.endsWith( ".JAR" ) || の場合
- path.endsWith( ".zip" ) || path.endsWith( ".ZIP" )) {
- 新しい ZipEntry(path)を返します。
- }
- 新しい DirEntry(path)を返します。
- }
- }
- このインターフェースは、インターフェース メソッド readClass と静的メソッド create(String path) を提供します。
- JDK 1.8 では、抽象クラスの同様の機能を補完するように設計された静的メソッドをインターフェースに記述できます。この静的メソッドは、主に、さまざまなパス アドレス タイプに応じて異なる解析メソッドを提供します。含まれるもの: CompositeEntry、WildcardEntry、ZipEntry、DirEntry、これら 4 つのタイプ。次に、それぞれの具体的な実装を見てみましょう。
2.2 ディレクトリ パス (DirEntry) - パブリッククラス DirEntry は Entry を実装します {
-
- プライベートパス absolutePath;
-
- パブリックDirEntry(文字列パス){
- //絶対パスを取得する
- 絶対パスを取得します。
- }
-
- @オーバーライド
- パブリックbyte[] readClass(String className)はIOExceptionをスローします{
- Files.readAllBytes(absolutePath.resolve(className))を返します。
- }
-
- @オーバーライド
- パブリック文字列toString() {
- this.absolutePath.toString()を返します。
- }
- }
ディレクトリ形式では、絶対パスの下のファイルを読み取り、Files.readAllBytes を通じてバイトコードを取得します。 2.3 Zipエントリ - パブリッククラス ZipEntry は Entry を実装します {
-
- プライベートパス absolutePath;
-
- パブリックZipEntry(文字列パス) {
- //絶対パスを取得する
- 絶対パスを取得します。
- }
-
- @オーバーライド
- パブリックbyte[] readClass(String className)はIOExceptionをスローします{
- 試してください (FileSystem zipFs = FileSystems.newFileSystem(absolutePath, null )) {
- Files.readAllBytes(zipFs.getPath(className))を返します。
- }
- }
-
- @オーバーライド
- パブリック文字列toString() {
- this.absolutePath.toString()を返します。
- }
-
- }
- 実際、圧縮パッケージ形式とディレクトリ形式の唯一の違いは、ファイル読み取り時のパッケージ化です。ファイルシステム.newFileSystem
2.4 複合エントリ - パブリッククラス CompositeEntry は Entry を実装します {
-
- プライベート最終リスト<Entry> entryList = 新しい ArrayList<>();
-
- パブリックCompositeEntry(文字列パスリスト) {
- 文字列[] パス = pathList.split(File.pathSeparator);
- for (文字列パス: パス) {
- エントリリスト。追加(エントリ。作成(パス));
- }
- }
-
- @オーバーライド
- パブリックbyte[] readClass(String className)はIOExceptionをスローします{
- for (エントリ entry : entryList) {
- 試す {
- entry.readClass(className)を返します。
- } catch (例外は無視されます) {
- //無視
- }
- }
- 新しい IOException をスローします ( "クラスが見つかりません" + className);
- }
-
-
- @オーバーライド
- パブリック文字列toString() {
- String[] strs = 新しいString[entryList.サイズ()];
- ( int i = 0; i < entryList.size ( ); i++) {
- strs[i] = entryList.get(i).toString();
- }
- String.join ( File.pathSeparator , strs)を返します。
- }
-
- }
- File.pathSeparator はセパレーター属性です。 Win/Linux では種類が異なるため、パスを分割するためにこの方法を使用します。
- 分割されたパスはリスト コレクションに格納され、このプロセスはパス分割に属します。
2.5 ワイルドカードエントリ - パブリッククラスWildcardEntryはCompositeEntryを拡張します{
-
- パブリックワイルドカードエントリ(文字列パス) {
- スーパー(toPathList(path));
- }
-
- プライベート静的文字列toPathList(文字列ワイルドカードパス) {
- 文字列 baseDir = wildcardPath。交換する( "*" 、 "" ); // 取り除く *
- 試す {
- Files.walk(Paths.get(baseDir))を返します
- .filter(ファイル::isRegularFile)
- .map(パス::toString)
- .filter(p -> p.endsWith( ".jar" ) || p.endsWith( ".JAR" ))
- .collect(Collectors.joining(File.pathSeparator));
- } キャッチ (IOException e) {
- 戻る "" ;
- }
- }
-
- }
- このクラスは混合形式パス処理クラスのサブクラスであり、提供される唯一の方法はクラスパスを解析することです。
2.6 クラスパス解決 スタートアップ クラス パス、拡張クラス パス、ユーザー クラス パスについてご存知ですか?これらの単語はあまり見かけないので、どのように実装すればよいのでしょうか? 上記で基本的なクラス作業を行ったので、次のステップはクラス解析の実際の呼び出しプロセスです。コードは次のとおりです。 - パブリッククラスクラスパス{
-
- プライベートエントリbootstrapClasspath; //スタートアップクラスパス
- プライベートエントリ extensionClasspath; //拡張クラスパス
- プライベートエントリ userClasspath; //ユーザークラスパス
-
- パブリッククラスパス(文字列 jreOption、文字列 cpOption) {
- //スタートアップクラスと拡張クラス"C:\Program Files\Java\jdk1.8.0_161\jre"
- bootstrapAndExtensionClasspath(jreOption);
- //ユーザークラス F:\..\org\itstack\demo\test\HelloWorld
- cpOption をパースします。
- }
-
- プライベート void bootstrapAndExtensionClasspath(String jreOption) {
-
- 文字列 jreDir = getJreDir(jreOption);
-
- //..jre/lib/*
- 文字列 jreLibPath = Paths.get(jreDir, "lib" ) + File.separator + "*" ;
- bootstrapClasspath = 新しい WildcardEntry(jreLibPath);
-
- //..jre/lib/ext/*
- 文字列 jreExtPath = Paths.get(jreDir, "lib" , "ext" ) + File.separator + "*" ;
- extensionClasspath = 新しい WildcardEntry(jreExtPath);
-
- }
-
- プライベート静的文字列 getJreDir(文字列 jreOption) {
- if (jreOption != null && Files.exists(Paths.get(jreOption))) {
- jreOptionを返します。
- }
- (Files.exists(Paths.get( "./jre" )))の場合{
- 戻る "./jre" ;
- }
- 文字列 jh = System.getenv( "JAVA_HOME" );
- jh != null の場合
- Paths.get(jh, "jre" ).toString()を返します。
- }
- 新しい RuntimeException をスローします ( "JRE フォルダーが見つかりません!" );
- }
-
- プライベートvoid parseUserClasspath(String cpOption) {
- cpOption == null の場合{
- cpOption = "." ;
- }
- userClasspath = エントリ。作成(cpOption);
- }
-
- パブリックbyte[] readClass(String className)は例外をスローします{
- クラス名 = クラス名 + ".class" ;
-
- //[readClass] 開始クラスパス
- 試す {
- bootstrapClasspath.readClass(className)を返します。
- } catch (例外は無視されます) {
- //無視
- }
-
- //[readClass] クラスパスを拡張する
- 試す {
- extensionClasspath.readClass(className)を返します。
- } catch (例外は無視されます) {
- //無視
- }
-
- //[readClass] ユーザークラスパス
- userClasspath.readClass(className)を返します。
- }
-
- }
- ブートクラスパス、bootstrapClasspath.readClass(className);
- 拡張クラスパス、extensionClasspath.readClass(className);
- ユーザー クラス パス、userClasspath.readClass(className);
- 今回は、具体的にどこで使われているのか見ていきましょう!具体的なコードを使用すると、理解しやすくなります。
2.7 クラスのテスト検証の読み込み - プライベート静的void startJVM(Cmd cmd) {
- クラスパス cp = new Classpath(cmd.jre, cmd.classpath);
- システム。 out .printf( "クラスパス: %s クラス: %s 引数: %s\n" , cp, cmd.getMainClass(), cmd.getAppArgs());
- //クラス名を取得
- 文字列 className = cmd.getMainClass()。交換する( "。" 、 "/" );
- 試す {
- byte[] classData = cp.readClass(className);
- システム。出力.println(Arrays.toString(classData));
- } キャッチ (例外 e) {
- システム。 out .println( "メインクラスが見つからないか、読み込めませんでした " + cmd.getMainClass());
- e.printStackTrace();
- }
- }
このセクションでは、Classpath クラスを使用してクラスパスを読み込みます。ここでは、java.lang.String クラスの読み込みをテストします。他のクラスや自分で書いたクラスをロードできる - IDEA、プログラム引数パラメータを設定します: -Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" java.lang.String
- さらに、ここで読み取られるクラス ファイル情報は、バイト型情報を出力します。
テスト結果 - [-54、-2、-70、-66、0、0、0、52、2、28、3、0、0、-40、0、3、0、0、-37、-1、3、0、0、-33、-1、3、0、1、0、0、8、0、15、8、0、 61、8、0、85、8、0、88、8、0、89、8、0、112、8、0、-81、8、0、-75、8、0、-47、8、0、-45、1、0、0、1、0、3、40、41、73、1、 0、20、 40、41、76、106、97、118、97、47、108、97、110、103、47、79、98、106、101、99、116、59、1、0、20、40、41、76、106、 97、118、97、47、108、97、110、103、47、83、116、114、105、110、103、59、1、0、3、40、41、86、1、0、3、40、41、90、1、 0、4、40、 41、91、…]
この部分で実行されているプログラムの印刷結果は、読み込んだクラスファイルの情報ですが、とりあえず何も表示されません。次は翻訳します! 5. バイトコードファイルの解析 JVM はクラス ファイルの読み込みを完了すると、コンテンツの検証、準備、解析を含むリンク プロセスに入ります。実際には、バイト型クラスを変換し、対応する操作を実行します。 プロセス全体には比較的多くのコンテンツが含まれるため、ここではロジックの一部のみを実装して説明します。興味のある読者は、Xiao Fu Ge のコラム「Java による JVM の実装」をお読みください。 1. バイトコードを抽出する - //バイトコードを取得: java.lang.String
- プライベート静的バイト[]クラスデータ = {
- -54、-2、-70、-66、0、0、0、52、2、26、3、0、0、-40、0、3、0、0、-37、-1、3、0、0、-33、-1、3、0、1、0、0、8、0、
- 59、8、0、83、8、0、86、8、0、87、8、0、110、8、0、-83、8、0、-77、8、0、-49、8、0、-47、1、0、3、40、41、73、1、
- 0、20、40、41、76、106、97、118、97、47、108、97、110、103、47、79、98、106、101、99、116、59、1、0、20、40、41、
- 76、106、97、118、97、47、108、97、110、103、47、83、116、114、105、110、103、59、1、0、3、40、41、86、1、0、3、
- 40、41、90、1、0、4、40、41、91、66、1、0、4、40、41、91、67、1、0、4、40、67、41、67、1、0、21、40、68、41、76、
- 106、97、118、97、47、108、97、110、103、47、83、116、114、105、110、103、59、1、0、4、40、73、41、67、1、0、4};
java.lang.String から解析されたバイトコードには、マジックナンバー、バージョン、クラス、定数、メソッドなど、多くのコンテンツが含まれています。したがって、ここでは分析のためにその一部だけを傍受します。 2. マジックナンバーを分析して検証する 多くのファイル形式では、その形式に適合するファイルは特定の固定バイトで始まる必要があると規定されています。これらのバイトは主に識別情報として機能し、マジックナンバーと呼ばれます。 例えば; - PDF ファイルは 4 バイトの「%PDF」(0x25、0x50、0x44、0x46) で始まります。
- ZIPファイルは2バイトの「PK」(0x50、0x4B)で始まります。
- クラスファイルは4バイトの「0xCAFEBABE」で始まります。
- プライベート静的void readAndCheckMagic() {
- システム。 out .println( "\r\n------------ マジックナンバーを確認してください ------------" );
- //クラスバイトコードから最初の4ビットを読み取ります
- byte[] magic_byte = 新しいbyte[4];
- System.arraycopy(classData, 0, magic_byte, 0, 4);
-
- //4ビットバイトを16進文字列に変換します
- 文字列 magic_hex_str = 新しい BigInteger(1, magic_byte).toString(16);
- システム。出力.println( "magic_hex_str: " + magic_hex_str);
-
- //byte_magic_strは16進文字列です、cafebabeさん、Javaには符号なし整数がないので、符号なし整数が必要な場合は上位ビットにのみ配置できます。
- long magic_unsigned_int32 = Long.parseLong(magic_hex_str, 16);
- システム。出力.println( "magic_unsigned_int32: " + magic_unsigned_int32);
-
- //マジックナンバーの比較。1つは文字列の比較によるもので、もう1つは想定される符号なし16進数の比較を使用します。符号なし比較を使用する場合は、0xCAFEBABEと0x0FFFFFFFFLをAND演算する必要があります。
- システム。出力.println( "0xCAFEBABE & 0x0FFFFFFFFL: " + (0xCAFEBABE & 0x0FFFFFFFFL));
-
- (magic_unsigned_int32 == (0xCAFEBABE & 0x0FFFFFFFFL))の場合{
- システム。 out .println( "クラス バイトコード マジックナンバー 符号なし 16 進値 一貫性チェックに合格しました" );
- }それ以外{
- システム。 out .println( "クラス バイトコード マジックナンバー 符号なし 16 進値 一貫性チェックが拒否されました" );
- }
- }
- バイトコードの最初の 4 ビット (-54、-2、-70、-66) を読み取り、16 進数に変換します。
- Java には符号なし整数がないため、上位ビットにのみ格納できます。
- 分析後、マジックナンバーが CAFEBABE と一致するかどうかが比較されます。
テスト結果 -
- magic_hex_str: カフェベイブ
- マジック_unsigned_int32:3405691582
- 0xCAFEBABE & 0x0FFFFFFFFFL: 3405691582
- クラス バイトコード マジックナンバー 符号なし 16 進数値 一貫性チェックに合格
3. バージョン番号情報の解析 マジックナンバー情報の 4 ビットを読み取りましたが、次の 2 ビットはバージョン情報です。 マジックナンバーの後には、クラスファイルのマイナーバージョン番号とメジャーバージョン番号が続きます。両方ともタイプは u2 です。クラス ファイルのメジャー バージョン番号が M、マイナー バージョン番号が m であると仮定すると、完全なバージョン番号は「Mm」の形式で表すことができます。マイナー バージョン番号は J2SE 1.2 より前でのみ使用され、1.2 以降では基本的に役に立たなくなりました (すべて 0)。 J2SE 1.2 より前のメジャー バージョン番号は 45 でした。 1.2 以降、Java のメジャーバージョンがリリースされるたびに 1 ずつ増加します {45、46、47、48、49、50、51、52} - プライベート静的void readAndCheckVersion() {
- システム。 out .println( "\r\n------------ バージョン番号を確認してください ------------" );
- //クラスバイトコードの4ビット目から読み取りを開始し、2ビット読み取ります
- byte[]minor_byte = 新しいバイト[2];
- System.arraycopy(classData, 4, minor_byte, 0, 2);
-
- //2ビットバイトを16進文字列に変換します
- 文字列 minor_hex_str = 新しい BigInteger(1, minor_byte).toString(16);
- システム。出力.println( "minor_hex_str: " + minor_hex_str);
-
- //minor_unsigned_int32 を符号なし 16 進数に変換
- int minor_unsigned_int32 =整数.parseInt(minor_hex_str, 16);
- システム。出力.println( "minor_unsigned_int32:" + minor_unsigned_int32);
-
- //クラスバイトコードの6ビット目から読み取りを開始し、2ビット読み取ります
- byte[] Major_byte = 新しいバイト[2];
- System.arraycopy(classData, 6, major_byte, 0, 2);
-
- //2ビットバイトを16進文字列に変換します
- 文字列 major_hex_str = 新しい BigInteger(1, major_byte).toString(16);
- システム。出力.println( "major_hex_str:" + major_hex_str);
-
- //major_unsigned_int32 を符号なし 16 進数に変換
- int major_unsigned_int32 =整数.parseInt(major_hex_str, 16);
- システム。出力.println( "major_unsigned_int32:" + major_unsigned_int32);
- システム。 out .println( "バージョン番号: " + major_unsigned_int32 + "." + minor_unsigned_int32);
- }
- ここにちょっとしたコツがあります。解析されたクラス ファイルは完全なコンテンツです。 JVM は、仮想マシンの仕様に従ってすべての情報を 1 つずつ解析する必要があります。
- ここでも、2 ビット バイトを 16 進情報に変換し、6 番目のビットから 2 ビットの情報を読み取る必要があります。結合バージョンはバージョン情報です。
テスト結果 -
- マイナー16進数: 0
- マイナー_unsigned_int32:0
- メジャー16進数: 34
- メジャー_unsigned_int32:52
- バージョン番号: 52.0
4. すべてのコンテンツを分析して比較する JVM のロード プロセスによると、実際にはマジック ナンバーやバージョン番号の情報以外にも多くの情報が存在します。ここでは、誰もが学習結果の比較印象を持てるように、テスト結果を表示できます。 - クラスパス: org.itstack.demo.jvm.classpath.Classpath@4bf558aa クラス: java.lang.String 引数: null
- バージョン: 52.0
- 定数数: 540
- アクセスフラグ: 0x31
- このクラス: java/lang/String
- スーパークラス:java/lang/Object
- インターフェース:[java/io/ Serializable , java/lang/Comparable, java/lang/CharSequence]
- フィールド数: 5
- 値 [C
- ハッシュI
- シリアルバージョンUID J
- serialPersistentFields [Ljava/io/ObjectStreamField;
- CASE_INSENSITIVE_ORDER Ljava/util/コンパレータ;
- メソッド数: 94
- <初期化> ()V
- <init> (Ljava/lang/String;)V
- <初期化> ([C)V
- <init> ([CII)V
- <init> ([III)V
- <init> ([BIII)V
- <init> ([BI)V
- チェックバウンド ([BII)V
- <init> ([BIILjava/lang/String;)V
- <init> ([BIILjava/nio/charset/Charset;)V
- <init> ([BLjava/lang/String;)V
- <init> ([BLjava/nio/charset/Charset;)V
- <init> ([BII)V
- <init> ([B)V
- <init> (Ljava/lang/StringBuffer;)V
- <init> (Ljava/lang/StringBuilder;)V
- <init> ([CZ)V
- 長さ ()I
- 空です ()Z
- 文字 (I)C
- コードポイントアット (I)I
- コードポイント前 (I)I
- コードポイントカウント (II)I
- コードポイントによるオフセット (II)I
- getChars([CI)V
- getChars (II[CI)V
- バイト取得 (II[BI)V
- getBytes (Ljava/lang/String;)[B
- getBytes (Ljava/nio/charset/Charset;)[B
- getBytes()[B
- (Ljava/lang/Object;)Zに等しい
- contentEquals (Ljava/lang/StringBuffer;)Z
- 非同期コンテンツEquals (Ljava/lang/AbstractStringBuilder;)Z
- contentEquals (Ljava/lang/CharSequence;)Z
- equalsIgnoreCase (Ljava/lang/String;)Z
- compareTo (Ljava/lang/String;)I
- compareToIgnoreCase (Ljava/lang/String;)I
- リージョンマッチ (ILjava/lang/String;II)Z
- リージョンマッチ (ZILjava/lang/String;II)Z
- (Ljava/lang/String;I)Zで始まります
- (Ljava/lang/String;)Zで始まります
- endsWith (Ljava/lang/String;)Z
- ハッシュコード ()I
- インデックスOf (I)I
- インデックス (II)I
- 補足索引(II)I
- 最後のインデックス (I)I
- 最後のインデックス (II)I
- 補足 (II)I の最終インデックス
- インデックスOf (Ljava/lang/String;)I
- インデックスOf (Ljava/lang/String;I)I
- インデックスOf ([CIILjava/lang/String;I)I
- インデックスOf ([CII[CIII)I
- lastIndexOf (Ljava/lang/String;)I
- lastIndexOf (Ljava/lang/String;I)I
- lastIndexOf ([CIILjava/lang/String;I)I
- 最後のインデックス ([CII[CIII)I
- 部分文字列(I)Ljava/lang/String;
- 部分文字列(II)Ljava/lang/String;
- サブシーケンス (II)Ljava/lang/CharSequence;
- 連結 (Ljava/lang/String;)Ljava/lang/String;
- (CC)Ljava/lang/String を置き換えます。
- (Ljava/lang/String;)Zに一致します
- (Ljava/lang/CharSequence;)Zを含む
- replaceFirst (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
- replaceAll (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
- (Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String を置き換えます。
- 分割 (Ljava/lang/String;I)[Ljava/lang/String;
- 分割 (Ljava/lang/String;)[Ljava/lang/String;
- (Ljava/lang/CharSequence;[Ljava/lang/CharSequence;)Ljava/lang/String;を結合します。
- join (Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String;
- toLowerCase (Ljava/util/Locale;)Ljava/lang/String;
- toLowerCase()Ljava/lang/String;
- ロケールを大文字に変換します。
- toUpperCase()Ljava/lang/String;
- トリム ()Ljava/lang/String;
- toString()Ljava/lang/String;
- toCharArray()[C
- フォーマットは (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
- フォーマットは (Ljava/util/Locale;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
- valueOf (Ljava/lang/Object;)Ljava/lang/String;
- valueOf ([C)Ljava/lang/String;
- valueOf ([CII)Ljava/lang/String;
- copyValueOf ([CII)Ljava/lang/String;
- copyValueOf ([C)Ljava/lang/String;
- (Z)Ljava/lang/String の値;
- (C)Ljava/lang/String の値;
- (I)Ljava/lang/String の値;
- (J)Ljava/lang/String の値;
- (F)Ljava/lang/String の値;
- (D)Ljava/lang/String の値;
- intern()Ljava/lang/String;
- compareTo (Ljava/lang/Object;)I
- <クライアント> ()V
-
- プロセスは終了コード 0で終了しました
検証、準備、解析のこの部分の実装プロセスに興味がある場合は、Java で実装された JVM ソース コードのこの部分を参照してください: https://github.com/fuzhengwei/itstack-demo-jvm VI.結論 JVM を学習する際の最大の問題は実践が難しいことです。そこでこの記事ではケーススタディを使用して JVM の読み込みと解析のプロセスを学習します。また、JVM に関心のある研究者や開発者が JVM にアクセスしやすくなり、JVM についてさらに詳しく学習できるようになります。 上記のコードを使用すると、JVM 仮想マシン仕様を参照して Java バージョンの JVM をデバッグできるため、JVM のロード プロセス全体とその動作を簡単に理解できます。 記事にオリジナルの画像やソースコードが必要な場合は、著者 Xiao Fu Ge (fustack) を追加するか、公開アカウント bugstack をフォローして入手してください。さて、この章はこれで終わりです。今後も色々努力して、オリジナル性は保ち続けて行きたいと思います。ご支援ありがとうございます! |