Heim  >  Artikel  >  Java  >  Implementierung der Java-Reflexion in JVM

Implementierung der Java-Reflexion in JVM

黄舟
黄舟Original
2017-02-20 10:36:211601Durchsuche

1. Was ist Java-Reflektion und wofür wird sie verwendet?

Reflection ermöglicht dem Programmcode den Zugriff auf die internen Informationen der in die JVM geladenen Klassen, sodass Code anstelle ausgewählter Klassen im Quellcode geschrieben und ausgeführt werden kann, wodurch Entwicklungseffizienz gegen betriebliche Effizienz eingetauscht wird. Dies macht Reflexion zu einem primären Werkzeug für die Erstellung flexibler Anwendungen.

Reflexion kann:

Einige private Methoden aufrufen, um schwarze Technologie zu erreichen. Zum Beispiel das Senden von Dual-SIM-Textnachrichten, das Festlegen der Farbe der Statusleiste, das automatische Auflegen des Telefons usw.

Implementieren Sie Serialisierung und Deserialisierung, z. B. ORM von PO, Json-Analyse usw.

Erzielen Sie plattformübergreifende Kompatibilität, z. B. die Implementierung von SocketImpl in JDK.

Erzielen Sie Abhängigkeitsinjektion (DI), Annotationsverarbeitung, dynamischen Proxy, Unit-Tests und andere Funktionen über XML oder Annotationen. Zum Beispiel Retrofit, Spring oder Dagger

2. Die Struktur der Java-Klassendatei

In der *.class-Datei wird die Klasse in Form eines Byte-Streams gespeichert Nach einer Reihe von Lade- und Analysevorgängen kann der Java-Code tatsächlich der Struktur in der folgenden Abbildung zugeordnet werden, die hier mithilfe des Befehls

javap

oder des IDE-Plug-Ins angezeigt werden kann.

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;

Konstantenpool: Ähnlich wie das DATA-Segment und das BSS-Segment in C stellt es Konstanten, Zeichenfolgen, Methodennamen und andere Werte oder Symbole bereit (kann als teilweise angesehen werden). Speicherung des Zeigers auf einen festen Wert)

access_flags: Flag-Änderung der Klasse

 typedef enum {
      ACC_PUBLIC = 0x0001,
      ACC_FINAL = 0x0010,
      ACC_SUPER = 0x0020,
      ACC_INTERFACE = 0x0200,
      ACC_ACSTRACT = 0x0400
  }AccessFlag

diese Klasse/Superklasse/Schnittstelle: ein Zeiger mit der Länge u2 , der auf zeigt Wenn es sich um eine echte Adresse im Konstantenpool handelt, wird das Symbol während der Link-Phase dereferenziert.

abgelegt: Feldinformationen, die Struktur ist wie folgt

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;

Methode: stellt Deskriptor, Zugriffsflags, Code und andere Indizes bereit und zeigt auf den Konstantenpool:

Seine Struktur ist wie folgt, Details hier

 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. Ladevorgang der Java-Klasse

Das Laden der Klasse ist hauptsächlich in zwei Schritte unterteilt

Der erste Schritt besteht darin, ClassLoader zu lesen und eine Verbindung herzustellen

Der zweite Schritt besteht darin, die Klasse zu initialisieren

<clinit>()

.

3.1. Classloader-Ladevorgang

ClassLoader wird zum Laden, Verbinden und Zwischenspeichern von Klassen verwendet, die über reines Java oder nativ implementiert werden können. Im nativen Code der JVM verwaltet ClassLoader intern einen Thread-sicheren

HashTable<String,Class>

, der zum Implementieren des Caches nach der Decodierung des Class-Byte-Streams verwendet wird. Wenn in der HashTable bereits ein Cache vorhanden ist, wird der ; Im Gegenteil, nachdem Sie den Klassennamen erhalten haben, deserialisieren Sie ihn in der nativen C-Struktur in der JVM, indem Sie den Klassenbyte-Stream in der Datei und im Netzwerk lesen, dann den Speicher mallocieren und den Zeiger in der HashTable zwischenspeichern .

Das Folgende ist der Prozess von ClassLoader in Nicht-Array-Situationen

Suchen/Laden: Deserialisieren Sie die Datei in eine C-Struktur.

Implementierung der Java-Reflexion in JVM

Klassen-Deserialisierungsprozess

Link: Dereferenzierungssymbole gemäß dem Konstantenpool der Klassenstruktur. Zum Beispiel Speicherplatz für Objektberechnungen, Erstellung einer Methodentabelle, eines nativen Aufrufers, einer Schnittstellenmethodentabelle, einer Finalizer-Funktion usw.

3.2. Initialisierungsprozess

Wenn der ClassLoader das Laden der Klasse abgeschlossen hat, wird die Klasse initialisiert. Führt hauptsächlich die statischen Codesegmente und statischen Variablen von

<clinit()>

aus (abhängig von der Quellcodesequenz).

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


Nachdem die Initialisierung abgeschlossen ist, ist es die Konstruktion des Objekts

<init>

, die hier nicht besprochen wird Artikel.

4. Implementierung von Reflection in Java

Reflection kann direkt in Java aufgerufen werden, aber der letzte Aufruf ist immer noch die native Methode. Das Folgende ist die Implementierung von Mainstream Reflexionsoperationen.

4.1. Implementierung von Class.forName

Class.forName kann Klassenobjekte über Paketnamen finden, z. B.

Class.forName("java.lang.String")

.

In der JDK-Quellcode-Implementierung können Sie feststellen, dass die native Methode

forName0()

letztendlich heißt. Was sie in der JVM tatsächlich aufruft, ist

findClassFromClassLoader()

. Prinzip Der gleiche Prozess wie bei ClassLoader, die spezifische Implementierung wurde oben vorgestellt.

4.2. Implementierung von getDeclaredFields

Im JDK-Quellcode können Sie erkennen, dass die Methode

class.getDeclaredFields()

tatsächlich die native Methode

getDeclaredFields0()
, seine wichtigsten Implementierungsschritte in JVM sind wie folgt:


Besorgen Sie sich entsprechend den Informationen zur Klassenstruktur die

field_count
und

fields[]
Felder. Während des Ladevorgangs wird


platziert, um Speicher entsprechend der Größe von

field_count
zuzuweisen. Erstellen Sie ein Array


, und führen Sie eine forEach-Schleife für das Array aus, bis

Die Informationen in
fields[]
erstellen wiederum Objektobjekte


und geben den Array-Zeiger zurück


主要慢在如下方面

创建、计算、分配数组对象

对字段进行循环赋值


4.3. Implementierung von Method.invoke

Die folgenden Schritte müssen ohne Synchronisierung und Ausnahme aufgerufen werden


Frame erstellen

Wenn das Objektflag nativ ist, übergeben Sie es zur Verarbeitung an native_handler

Führen Sie Java-Code im Frame aus

Öffnen Sie den Frame

Geben Sie den Zeiger der Ausführung zurück Ergebnis


主要慢在如下方面

需要完全执行ByteCode而缺少JIT等优化

检查参数非常多,这些本来可以在编译器或者加载时完成


4.4 Die Implementierung von class.newInstance

Erkennt Berechtigungen, vorab zugewiesene Speicherplatzgröße und andere Parameter


Erstellt Objektobjekte und weist Speicherplatz zu

Ruft den Konstruktor über Method.invoke auf (

<init>()
)


Objektzeiger zurückgeben


主要慢在如下方面

参数检查不能优化或者遗漏的查表

Method.invoke本身耗时


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)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn