Javaのスキンを脱いで、その心を見てください

この記事のタイトルは変です、説明させてください。

これは私自身の経験です。つまり、原則を理解するときは、常にそれをたいと思っています。

CPUの原理を研究するとき、各命令が実行されたときにCPU内の電気信号がどのように進むかたいと思います。

オペレーティングシステムの原理を研究するとき、コンピュータの起動からオペレーティングシステムのロードまで、メモリレイアウトで実際にどのような変更が行われたか確認したいと思います。

同じ方法でJavaの原則を勉強するとき、私はしたい参照して、各Javaコードが最終的に翻訳されたものを機械語命令、と私はしたい何を参照してくださいなどのメモリ、のようにJavaオブジェクトのルックス...

要するに、私はただ見たいです!

この種の絡み合いが常にあり、Java言語レベルと、インターネット上のいくつかのブログで「伝えられている」原則だけにとどまりたくない場合は、今日 、Javaを確認するためのヒントをいくつか 紹介します。さまざまなレベルから原則を分析します。

  • javap:バイトコードを参照

  • strace:システム呼び出しを参照

  • hsdis + jitwatch:マシンコードを参照してください

  • openJDK:ネイティブメソッドを参照

  • JOL:オブジェクトを見る

  • fastthread:スレッドを参照

  • JProfile:実行時にさまざまな状態を確認する

javap:バイトコードを参照

このコマンドは誰もが知っているはずです。Javaに一定期間連絡した後は、言語レベルの原則を理解することに常に不満があります。現時点では、バイトコードを通じて基本的な原則のいくつかをより深く理解する必要があります

この時点で、この javap コマンドは非常に重要です。実際には.classファイル全体を解析しますが、通常は.classファイル内のバイトコード命令を分析するために使用します。

コマンドの使用方法は?オンラインで検索してください。IDEAのプラグインを使用することで、バイトコードを学習する最もわかりやすい方法です。彼をバイトコード学習アーティファクトjclasslibと呼ぶことができ ます

プラグインをダウンロードすると、表示メニューにもう1つ項目が表示されます。

それをクリックして、現在のクラスのバイトコードを分析します

このステップは、Javaの基本的な原則を理解するための最初のレイヤーを超えて、最後に進みます〜

strace:システム呼び出しを参照

JVMレベルから原理を理解すると、味わいが足りないと感じるかもしれませんが、JVMの最下層がどのように実装されているのかを知りたいです。しかし、最終的な機械の指示に直接行きたくない場合は、実際にはシステム呼び出しである優れた中間層があります

システム呼び出しを理解していない場合は、最初にオペレーティングシステムの知識を確認できます。

strace コマンドは、特定のプログラム実行プロセス中に開始されたシステム呼び出しを表示でき、このプロセスはリアルタイムです。

このコマンドを使用して、システム呼び出しが行われるJavaの従来のBIOサーバープログラムを見てみましょう。このレベルから、BIOの原理を簡単に分析すると、この観点は、従来の観点に対する次元の削減の打撃あることがわかります。アップ。

従来のBIOのプロセスを見てください

ステップ1:簡単なソケットサーバープログラムを作成し、ポート8080を開いてリッスンします

public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(8080);
    Socket socket = serverSocket.accept();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    System.out.println(bufferedReader.readLine());
}

ステップ2:straceコマンドを使用して、プログラムの開始後にシステム呼び出しを表示します(ここでは、ネットワーク関連のシステム呼び出しのみを確認します)。

[bash〜] #strace -ff -e trace = network java SocketDemo

...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50)               = 0

実際、ソケットサーバープログラムがLinuxオペレーティングシステムを3回のシステム呼び出し socket起動しbindその後ことがわかりますlistenこの時点では、Javaプロセスは終了せず、ブロックが発生したことを示します(具体的には、ブロックはJavaコードのaccept文でブロックされます)

手順3:ncコマンドを使用してポート8080に接続します

[bash〜] #nc localhost 8080

...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50)               = 0
[pid 28226] accept(5, {sa_family=AF_INET6, sin6_port=htons(11103), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
[pid 28226] recvfrom(6,

TCP接続ncコマンドソケットサーバー、システムがmoreaccept を 呼び出しrecvfrom、それらがダウンしなくなったことがわかります が、今回はJavaプロセスが終了しておらず、ブロッキングで発生したことを示しています(今回はブロッキングJavaコードのreadlineは、システムが呼び出されたときのrecvfromに対応します)

ステップ4:今すぐncコマンドで文字列「hello」を渡し続け、Enterキーを押して送信します

[bash〜] #nc localhost 8080

こんにちは

...
[pid 28226] socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 5
...
[pid 28226] bind(5, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
[pid 28226] listen(5, 50)               = 0
[pid 28226] accept(5, {sa_family=AF_INET6, sin6_port=htons(11103), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 6
[pid 28226] recvfrom(6, "hello\n", 8192, 0, NULL, NULL) = 6
hello
+++ exited with 0 +++

recvfrom関数が完了したばかりで、戻り値があり、プログラム全体が終了していることがわかります。

strace このコマンドにより 、JavaのBIO プロセスを下位レベルの観点から包括的に理解 できます。オペレーティングシステムも理解し、Linuxでのさまざまなシステム呼び出しの詳細に精通している場合、このような小さな実験はほぼ完全に役立つと言えます。 BIOの原則をマスターします。同じこと  がNIOにも当てはまります。これは、JavaレベルからBIO NIOを単に理解するよりも、次元の削減の打撃ではありませんか?

もちろん、多くの方法はシステム呼び出しを使用しません。コア原則がシステム呼び出しで構成されていない場合、この方法は研究には適していません。これが究極の方法です。肉眼でアセンブリ読むことができる場合は、次の方法を使用できます。」理論的には、すべての原則を理解できます。

HSDIS + JITWATCH:マシンコード(アセンブリ)を参照

これは、最も次元を減らす方法の1つです。Javaコードが最終的にCPUレベルで実行するマシン命令を直接確認でき ます

この記事を通じて、この環境をう​​まく構築できます。

https://zhuanlan.zhihu.com/p/158168592?from_voters_page=true

最終的な効果は次のとおりです

もちろん、この方法は少し深すぎます。現在のプログラムや複雑なJVMの場合、マシンコードを見ても全体像を理解することはできません特定のコード行の最も重要な実行の詳細を差し引くとしか言えません。その時、この方法があることを知ってください。

実装の最低レベルを理解したい場合は、ソースコードがあるため、実行されているマシンコードを監視する必要はありません。たとえば、ネイティブメソッド、またはsychronizedキーワードの仮想マシン実装原則では、次のメソッドが適しています。

openJDK:ネイティブメソッドを参照

jdkソースコードを読んで、時々ネイティブ メソッドに来るとき 、それは私が長い間無駄になっていたことを意味するので、私はしばしば必死になります。

public class Object {
    ...
    public native int hashCode();
    ...
}

ネイティブメソッドの基礎となる実装を本当に理解したい場合は、openJDKのソースコードを実際にダウンロードできます :https://github.com/openjdk/jdk

ソースコードを取得してから最初に入力すると ./jdk/src/share/native、次のディレクトリ構造が表示されます。

一般

java

太陽

javaディレクトリに入った後

lang

ネット

ニオ

セキュリティ

有用

基本的にjdkディレクトリと同じであることがわかりましたか?それからあなたは基本的にそれをチェックする方法を知っています。

たとえば、Objectクラスのハッシュコードメソッドを検索する場合、jdkのObjectのディレクトリに従って、そのネイティブメソッドのディレクトリを検索できます。

./jdk/src/share/native/java/lang/Object.c

...
#include "java_lang_Object.h"

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};
...

もう一度フォローダウンします。9回のベンドと18回のベンドの後、このメソッドが実行されます openjdk\hotspot\src\share\vm\runtime\synchronizer.cpp --> FastHashCode。次のコードでは、10万行が省略されています。

intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
  ...
  hash = mark->hash();
  if (hash == 0) {
    hash = get_next_hash(Self, obj);
    temp = mark->copy_set_hash(hash); // merge hash code into header
    ...
  }
  ...
  // We finally get the hash
  return hash;

ネイティブメソッドには多くのC ++コードがあるため、ソースコードを自分で読んでネイティブの基本的な実装を本当に理解したい場合は、C ++の基本的な構文を学ぶことができます。

もちろん、openJDKは、仮想マシンのいくつかの実装原則を確認することもできます。たとえば、 sychronized ここでは拡張しない仮想マシンレベルでのキーワードの実装を確認します。

JOL:オブジェクトを見る

インタビューでは常にメモリ内のオブジェクトのレイアウトについて尋ねられますがオブジェクトのメモリレイアウトを直接確認する直感的な方法はありますか?そして、オブジェクトのメモリ内のデータの変化を観察するために実験を続けることができます。

もちろんあり、公式ですが、openJDKが提供する公式ツールJOLです 

使い方はとても簡単で、mavenが紹介するだけです。

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>put-the-version-here</version>
</dependency>

次に、ClassLayout.parseInstance(o).toPrintable()次のように、コードを直接呼び出すことができ ます。

public static void main(String[] args) {
    Object o = new Object();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

コンソール出力を見ると、オブジェクトヘッダーのバイナリ表現を直接確認できますオブジェクトにはメンバー変数がないため、メンバー変数は反映されません。

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

とても直感的ですか?以来、ロック情報は、オブジェクトヘッダに格納され、我々が見るためにこのツールを使用することができるのプロセスロックアップグレード自分で行った後、ロックのアップグレードは直感的になります。

ロックのアップグレードのプロセスを見てください

ロック情報はオブジェクトヘッダーのマークワードに記録されているため、独自の実験を行ってさまざまな段階でオブジェクト情報を印刷すること により、ロックのアップグレードのプロセスを確認できます 

ここにコードを書きました。少し長くて貼り付けられません。スタックしていると、クリックして開くことすらできません。

出力結果のみを入力しました。情報が多すぎて、ロックタイプを示す情報のみが取得されました。

  • 新しく始めたばかり:(001ロックなし)

  • 新しいものを5秒間待ちます:(101匿名バイアスロック)

  • メインスレッドはロックを追加します(:101バイアスロック)

  • メインスレッドがロックを解除します:101バイアスロック)

  • 新しいスレッドロック:(00軽量ロック)

  • 新しいスレッドはロックを解除して終了します:(001ロックなし)

  • 2つのスレッドがロックをつかみます:(10ヘビーウェイトロック)

  • 両方のスレッドが終了します:(10ヘビーウェイトロック)

  • ちょっと待ってください:(001ロックなし)

fastthread:スレッドを参照

jstack コマンドを使用て、現在のJVMのスレッドスナップショットをファイルにダンプできることはわかってい ます。

しかし、このファイルは直感的ではなく、大量のテキストであり、視覚化するためのツールはたくさんありますが、私が使用する最も便利で美しい視覚化ツールは fastthread であり、より便利なのはWebページであるということです

とにかく、初めてこのサイトにアクセスしたときはとてもきれいでした。公式ウェブサイトから、それは非常に技術的で非常に興味深いものであり、人々は分析と分析のためにダンプファイルをアップロードしたいと思うようになります。

jstackによって生成されたダンプファイルをアップロードしました。しばらく分析した後、非常に美しいページを見ることができます。

スレッドの全体的な状況を表示する

スレッドのグループ化を表示

スレッドの詳細を表示する

まだまだたくさんありますので、一つ一つはお見せしません。公式サイトはとてもフレンドリーなので、買い物に行くことができます〜

JProfile:実行時にさまざまな状態を確認する

JVMのリアルタイムのステータスを確認することは、システムレベルからプログラムの実行プロセスを理解するのに非常に役立ちます。jdkに付属の視覚化ツールJConsoleを使用したことがあるでしょうか。

それは少し醜いです、そして機能はあまり強力ではないので、JProfileがあり ます

JProfiler は、ej-technologies GmbHによって開発されたパフォーマンスボトルネック分析ツールです。率直に言って、JConsoleの高度な美化バージョンです。最初にその外観を評価できます。

ここでは、このようなプログラムを使用して実行します。このプログラムは引き続きヒープメモリを占有し、gcになることはできず、最後にOOMになることが予想されます。

public static void main(String[] args) throws Exception {
    List<Byte[]> list = new ArrayList<>();
    while (true) {
        list.add(new Byte[1024*1024]);
        Thread.sleep(100);
    }
}

JProfileの包括的なページを開き、パフォーマンス情報をリアルタイムでフォローアップします

最後に、JavaプログラムもOOMを報告しました

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

オブジェクト監視パネルですべてのオブジェクト情報を表示し、指定されたルールに従って並べ替えることができます

もちろん、この例では、(最大オブジェクト)内のビッグオブジェクト分析ビューを直接使用できます。

このようにして、最大のメモリスペースは成長するArrayListオブジェクトであることがわかります。

もちろん、スレッドの分析にも使用できますが、fastthread Webサイトで特定の瞬間のスナップショットを分析する方が、より直感的で強力です。

JProfileは、プラグインを介してIDEAに直接接続することもできます。最終的な効果は、JProfileをクリックして使用してIDEAで実行することです。これにより、JProfile監視プログラムに自動的にジャンプします。

もう1分話してください

javap バイトコードを閲覧 strace し、 HSDIS 読むことによって、コンピュータとの下位レベルの相互作用を見て、 openJDK ソース保持後、ネイティブとJVMの実装方法を理解するために、そして Java 语言规范 そして 虚拟机规范 理論的には、公式文書を、あなたはそれが自分のJava言語を働いたと言うことができますそれの全体の原則したがって、これらのガジェットを使用して自分でより多くの実験を行うことは非常に役立ちます。ブログの投稿を読んだり、本を読んだりするよりも、ある程度深く理解することができます。

Javaの紹介に関するいくつかのヒントはここにあります。それでもこれが好きな場合  は、ツールやテクニックの種類を見てください。同様のツールがたくさんある場合は、コメントセクションでの議論を歓迎します。補足バージョンを作成できます。みなさんお願いします〜

最後に、それに投票してください!

おすすめ

転載: blog.csdn.net/coderising/article/details/110914000