JVM での Java リフレクションの実装
1. Java リフレクションとは何ですか?何に使用されますか?
リフレクションにより、プログラム コードが JVM にロードされたクラスの内部情報にアクセスできるようになり、ソース コード内ではなく、作成中および実行中に選択されたクラスとコードを連携できるようになり、開発効率を運用効率に変える手段となります。このため、リフレクションは柔軟なアプリケーションを構築するための主要なツールになります。
リフレクションでは次のことが可能です:
ブラック テクノロジーを実現するためにいくつかのプライベート メソッドを呼び出します。たとえば、デュアル SIM テキスト メッセージの送信、ステータス バーの色の設定、電話の自動切断などです。
POのORM、Json解析などのシリアル化と逆シリアル化を実装します。
JDKでのSocketImplの実装など、クロスプラットフォーム互換性を実現
xmlやアノテーションを通じて、依存性注入(DI)、アノテーション処理、動的プロキシ、単体テストなどの機能が実装されます。 Retrofit、Spring、Dagger など
2. Java クラス ファイルの構造
*.class ファイルでは、クラスは一連のロードと解析の後にバイト ストリームの形式で保存されます。実際にマッピングされるのは下の図の構造で、
javap
コマンドまたは IDE プラグインを使用してここで表示できます。
typedef struct { u4 magic;/*0xCAFEBABE*/ u2 minor_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; field_info fields[fields_count]; //重要 u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }ClassBlock;
定数プール: C の DATA セグメントや BSS セグメントと同様に、定数、文字列、メソッド名、その他の値またはシンボルのストレージを提供します (オフセット固定値を持つポインターとみなすことができます)
access_flags : Class
typedef enum { ACC_PUBLIC = 0x0001, ACC_FINAL = 0x0010, ACC_SUPER = 0x0020, ACC_INTERFACE = 0x0200, ACC_ACSTRACT = 0x0400 }AccessFlag
このクラス/スーパークラス/インターフェイスのフラグ変更: 定数プール内の実際のアドレスを指す、長さ u2 のポインター。リンクフェーズ中に逆参照されます。
filed: フィールド情報、構造は次のとおりです
typedef struct fieldblock { char *name; char *type; char *signature; u2 access_flags; u2 constant; union { union { char data[8]; uintptr_t u; long long l; void *p; int i; } static_value; u4 offset; } u; } FieldBlock;
メソッド: 記述子、access_flags、Code などのインデックスを提供し、定数プールを指します:
その構造は次のとおり、詳細はこちら
method_info { u2 access_flags; u2 name_index; //the parameters that the method takes and the //value that it return u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
以上具体内容可以参考 JVM文档 周志明的《深入理解Java虚拟机》,少见的国内精品书籍 一些国外教程的解析
3. Javaクラスロードのプロセス
クラスロードは主に2つのステップに分かれています
最初のステップはClassLoaderを介して読み取って接続することです
2番目のステップはクラスを初期化することです
<clinit>()
。
3.1. クラスローダーのロードプロセス
ClassLoader は、純粋な Java またはネイティブを通じて実装できるクラスのロード、接続、およびキャッシュに使用されます。 JVM のネイティブ コードでは、ClassLoader は内部でスレッドセーフな
HashTable<String,Class>
を維持します。これは、クラス バイト ストリームをデコードした後にキャッシュを実装するために使用されます。HashTable にキャッシュがすでに存在する場合は、キャッシュが直接返されます。それ以外の場合は、クラスの取得後 名前が作成された後、ファイルおよびネットワーク上のクラス バイト ストリームが JVM 内のネイティブ C 構造に逆シリアル化され、メモリが割り当てられ、ポインタが HashTable にキャッシュされます。
以下は配列以外の場合の ClassLoader のプロセスです
find/load: ファイルを C 構造体に逆シリアル化します。
クラス逆シリアル化プロセス
link: クラス構造定数プールに基づいてシンボルを逆参照します。例えば、オブジェクト計算のメモリ空間、メソッドテーブルの作成、ネイティブインボーカー、インターフェースメソッドテーブル、ファイナライザー関数など。
3.2. 初期化プロセス
ClassLoaderがクラスのロードを完了すると、クラスが初期化されます。主に
<clinit()>
の静的コードセグメントと静的変数を実行します (ソースコードのシーケンスに応じて)。
public class Sample { //step.1 static int b = 2; //step.2 static { b = 3; } public static void main(String[] args) { Sample s = new Sample(); System.out.println(s.b); //b=3 } }
具体参考如下: When and how a Java class is loaded and initialized? The Lifetime of a Type
初期化が完了したら、オブジェクト
<init>
の構築ですが、この記事では説明しません。
4. ネイティブでのリフレクションの実装
リフレクションは Java で直接呼び出すことができますが、最後の呼び出しは依然としてネイティブ メソッドです。以下は、主流のリフレクション操作の実装です。
4.1. Class.forName の実装
Class.forName は、
Class.forName("java.lang.String")
などのパッケージ名を通じて Class オブジェクトを検索できます。
JDK ソース コードの実装では、ネイティブ メソッド
forName0()
が JVM で呼び出される実際のメソッドは、ClassLoader プロセスと同じです。上で紹介されています。
4.2. getDeclaredFields の実装
JDK ソース コードでは、
findClassFromClassLoader()
メソッドが実際に JVM でのネイティブ メソッド
class.getDeclaredFields()
を呼び出すことがわかります。クラス構造情報、
getDeclaredFields0()
および
field_count
フィールドを取得します。このフィールドはロードプロセス中にすでに配置されています
メモリを割り当て、
fields[]
のサイズに応じて配列を作成します 配列に対してforEachループを実行します
field_count
の情報を基にObjectオブジェクトを順番に作成します
fields[]
4.3 Method.invokeの実装
フレームを作成
オブジェクトフラグがネイティブの場合はnative_handlerに渡す 処理
フレーム内のJavaコードを実行
フレームをポップアップ
実行結果のポインタを返す
主要慢在如下方面 创建、计算、分配数组对象 对字段进行循环赋值
4.4の実装。 class.newInstanceの
権限、事前に割り当てられた領域サイズ、その他のパラメータを検出
オブジェクトオブジェクトを作成し、領域を割り当てる
Method.invokeを通じてコンストラクタ(
主要慢在如下方面 需要完全执行ByteCode而缺少JIT等优化 检查参数非常多,这些本来可以在编译器或者加载时完成
)を呼び出す
オブジェクトポインタを返す
<init>()
5. 附录
5.1. JVM与源码阅读工具的选择
初次学习JVM时,不建议去看Android Art、Hotspot等重量级JVM的实现,它内部的防御代码很多,还有android与libcore、bionic库紧密耦合,以及分层、内联甚至能把编译器的语义分析绕进去,因此找一个教学用的、嵌入式小型的JVM有利于节约自己的时间。因为以前折腾过OpenWrt,听过有大神推荐过jamvm,只有不到200个源文件,非常适合学习。
在工具的选择上,个人推荐SourceInsight。对比了好几个工具clion,vscode,sublime,sourceinsight,只有sourceinsight对索引、符号表的解析最准确。
5.2. 关于几个ClassLoader
参考这里
ClassLoader0:native的classloader,在JVM中用C写的,用于加载rt.jar的包,在Java中为空引用。
ExtClassLoader: 用于加载JDK中额外的包,一般不怎么用
AppClassLoader: 加载自己写的或者引用的第三方包,这个最常见
例子如下
//sun.misc.Launcher$AppClassLoader@4b67cf4d //which class you create or jars from thirdParty //第一个非常有歧义,但是它的确是AppClassLoader ClassLoader.getSystemClassLoader(); com.test.App.getClass().getClassLoader(); Class.forName("ccom.test.App").getClassLoader() //sun.misc.Launcher$ExtClassLoader@66d3c617 //Class loaded in ext jar Class.forName("sun.net.spi.nameservice.dns.DNSNameService") //null, class loaded in rt.jar String.class.getClassLoader() Class.forName("java.lang.String").getClassLoader() Class.forName("java.lang.Class").getClassLoader() Class.forName("apple.launcher.JavaAppLauncher").getClassLoader()
最后就是
getContextClassLoader()
,它在Tomcat中使用,通过设置一个临时变量,可以向子类ClassLoader去加载,而不是委托给ParentClassLoader
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // call some API that uses reflection without taking ClassLoader param } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); }
最后还有一些自定义的ClassLoader,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。
5.3. 反射是否慢?
在Stackoverflow上认为反射比较慢的程序员主要有如下看法
验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证
产生很多临时对象,造成GC与计算时间消耗
由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)
当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。
更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。
以上就是Java反射在JVM的实现 的内容,更多相关内容请关注PHP中文网(www.php.cn)!

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

Java は、初心者と経験豊富な開発者の両方が学習できる人気のあるプログラミング言語です。このチュートリアルは基本的な概念から始まり、高度なトピックに進みます。 Java Development Kit をインストールしたら、簡単な「Hello, World!」プログラムを作成してプログラミングを練習できます。コードを理解したら、コマンド プロンプトを使用してプログラムをコンパイルして実行すると、コンソールに「Hello, World!」と出力されます。 Java の学習はプログラミングの旅の始まりであり、習熟が深まるにつれて、より複雑なアプリケーションを作成できるようになります。
