Maison > Java > javaDidacticiel > Implémentation de la réflexion Java dans JVM

Implémentation de la réflexion Java dans JVM

黄舟
Libérer: 2017-02-20 10:36:21
original
1655 Les gens l'ont consulté

1. Qu'est-ce que la réflexion Java et à quoi sert-elle ?

Reflection permet au code du programme d'accéder aux informations internes des classes chargées dans la JVM, permettant ainsi d'écrire et d'exécuter du code à la place des classes sélectionnées dans le code source, échangeant ainsi l'efficacité du développement contre l'efficacité opérationnelle. Cela fait de la réflexion un outil principal pour créer des applications flexibles.

La réflexion peut :

Appeler certaines méthodes privées pour parvenir à la technologie noire. Par exemple, envoyer des messages texte double SIM, définir la couleur de la barre d'état, raccrocher automatiquement le téléphone, etc.

Implémentez la sérialisation et la désérialisation, telles que l'ORM du PO, l'analyse Json, etc.

Obtenir une compatibilité multiplateforme, telle que l'implémentation de SocketImpl dans JDK

Réaliser l'injection de dépendances (DI), le traitement des annotations, le proxy dynamique, les tests unitaires et d'autres fonctions via XML ou annotations. Par exemple, Retrofit, Spring ou Dagger

2. La structure du fichier de classe Java

Dans le fichier *.class, la classe est stockée sous forme de flux d'octets. , grâce à une série de chargements et d'analyses, le code Java peut en fait être mappé à la structure de la figure ci-dessous, qui peut être visualisée ici à l'aide de la commande

javap
Copier après la connexion

ou du plug-in 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;
Copier après la connexion

Pool de constantes : similaire au segment DATA et au segment BSS en C, il fournit des constantes, des chaînes, des noms de méthodes et d'autres valeurs ou symboles (peuvent être considérés comme partiels Stockage du pointeur vers une valeur fixe)

access_flags : modification du drapeau de la classe

 typedef enum {
      ACC_PUBLIC = 0x0001,
      ACC_FINAL = 0x0010,
      ACC_SUPER = 0x0020,
      ACC_INTERFACE = 0x0200,
      ACC_ACSTRACT = 0x0400
  }AccessFlag
Copier après la connexion

cette classe/super classe/interface : un pointeur de longueur u2 , pointant vers l'adresse réelle dans le pool de constantes, le symbole sera déréférencé lors de la phase Link.

archivé : informations sur le champ, la structure est la suivante

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;
Copier après la connexion

méthode : fournit le descripteur, les access_flags, le code et d'autres index, et pointe vers le pool de constantes :

Sa structure est la suivante, détails ici

 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];
  }
Copier après la connexion
以上具体内容可以参考

JVM文档

周志明的《深入理解Java虚拟机》,少见的国内精品书籍

一些国外教程的解析
Copier après la connexion


Processus de chargement de la classe Java

Le chargement de la classe est principalement divisé en deux étapes

La première étape consiste à lire et à se connecter via ClassLoader

La deuxième étape consiste à initialiser la classe

<clinit>()
Copier après la connexion

.

3.1. Processus de chargement de Classloader

ClassLoader est utilisé pour charger, connecter et mettre en cache une classe, qui peut être implémentée via Java pur ou natif. Dans le code natif de la JVM, ClassLoader maintient un

HashTable<String,Class>
Copier après la connexion

thread-safe en interne, qui est utilisé pour implémenter le cache après décodage du flux d'octets de la classe. S'il existe déjà un cache dans la HashTable, le. le cache est renvoyé directement ; Au contraire, après avoir obtenu le nom de la classe, désérialisez-le dans la structure C native dans la JVM en lisant le flux d'octets de la classe sur le fichier et le réseau, puis mallocez la mémoire et mettez le pointeur en cache dans la HashTable. .

Ce qui suit est le processus de ClassLoader dans des situations sans tableau

trouver/charger : Désérialiser le fichier dans une structure C.

Implémentation de la réflexion Java dans JVM

Processus de désérialisation de classe

lien : Symboles de déréférencement selon le pool constant de structure de classe. Par exemple, espace mémoire de calcul d'objet, création de table de méthodes, invocateur natif, table de méthodes d'interface, fonction de finaliseur, etc.

3.2. Processus d'initialisation

Lorsque le ClassLoader aura fini de charger la classe, la classe sera initialisée. Exécute principalement les segments de code statique et les variables statiques de

<clinit()>
Copier après la connexion

(en fonction de la séquence du code source).

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
  }
}
Copier après la connexion
具体参考如下:

When and how a Java class is loaded and initialized?

The Lifetime of a Type
Copier après la connexion


Une fois l'initialisation terminée, c'est la construction de l'Objet

<init>
Copier après la connexion

, qui ne sera pas abordée dans ce article.

4. Implémentation de la réflexion en natif

La réflexion peut être appelée directement en Java, mais l'appel final est toujours la méthode native. Ce qui suit est l'implémentation du courant dominant. opérations de réflexion.

4.1. Implémentation de Class.forName

Class.forName peut trouver des objets Class via des noms de packages, tels que

Class.forName("java.lang.String")
Copier après la connexion

.

Dans l'implémentation du code source du JDK, on ​​peut constater que la méthode native

forName0()
Copier après la connexion

est finalement appelée. Ce qu'elle appelle réellement dans la JVM est

findClassFromClassLoader()
Copier après la connexion
<🎜. >.Principe Le même processus que ClassLoader, l'implémentation spécifique a été présentée ci-dessus.



4.2. Implémentation de getDeclaredFields

Dans le code source du JDK, vous pouvez savoir que la méthode

class.getDeclaredFields()
Copier après la connexion
appelle en fait la méthode native

🎜>
getDeclaredFields0()
Copier après la connexion

, ses principales étapes d'implémentation dans JVM sont les suivantes :

Selon les informations sur la structure de la classe, obtenez le

field_count
Copier après la connexion
Copier après la connexion

et le

fields[]
Copier après la connexion
Copier après la connexion

champs. Ce champ a déjà été Pendant le processus de chargement,

est placé pour allouer de la mémoire en fonction de la taille de

field_count
Copier après la connexion
Copier après la connexion

, créer un tableau

, et effectuez une boucle forEach sur le tableau, via

fields[]
Copier après la connexion
Copier après la connexion
Les informations contenues dans

créent des objets Object en séquence

et renvoie le pointeur du tableau

主要慢在如下方面

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

对字段进行循环赋值
Copier après la connexion


4.3. Implémentation de Method.invoke

Voici les étapes à appeler sans synchronisation ni exception

Créer un cadre

Si l'indicateur de l'objet est natif, remettez-le à native_handler pour traitement

Exécuter le code Java dans le cadre

Faire apparaître le cadre

Renvoyer le pointeur de l'exécution result

主要慢在如下方面

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

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


4.4 L'implémentation de class.newInstance

Détecte les autorisations, la taille de l'espace pré-alloué et d'autres paramètres.

Crée des objets objet et alloue de l'espace

Appelle le constructeur via Method.invoke (

<init>()
Copier après la connexion

)

Retour du pointeur d'objet

主要慢在如下方面

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

Method.invoke本身耗时
Copier après la connexion


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()
Copier après la connexion

最后就是

getContextClassLoader()
Copier après la connexion

,它在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);
}
Copier après la connexion

最后还有一些自定义的ClassLoader,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。

5.3. 反射是否慢?

在Stackoverflow上认为反射比较慢的程序员主要有如下看法

验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证

产生很多临时对象,造成GC与计算时间消耗

由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)

当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。

更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。

 以上就是Java反射在JVM的实现 的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal