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
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
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;
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
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;
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]; }
以上具体内容可以参考 JVM文档 周志明的《深入理解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>()
.
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>
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.
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.
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()>
(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 } }
具体参考如下: When and how a Java class is loaded and initialized? The Lifetime of a Type
Une fois l'initialisation terminée, c'est la construction de l'Objet
<init>
, qui ne sera pas abordée dans ce article.
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.
Class.forName peut trouver des objets Class via des noms de packages, tels que
Class.forName("java.lang.String")
.
Dans l'implémentation du code source du JDK, on peut constater que la méthode native
forName0()
est finalement appelée. Ce qu'elle appelle réellement dans la JVM est
findClassFromClassLoader()
class.getDeclaredFields()
getDeclaredFields0()
, ses principales étapes d'implémentation dans JVM sont les suivantes :
Selon les informations sur la structure de la classe, obtenez le
field_count
et le
fields[]
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
, créer un tableau
, et effectuez une boucle forEach sur le tableau, via
fields[]
créent des objets Object en séquence
et renvoie le pointeur du tableau
主要慢在如下方面 创建、计算、分配数组对象 对字段进行循环赋值
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等优化 检查参数非常多,这些本来可以在编译器或者加载时完成
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>()
)
Retour du pointeur d'objet
主要慢在如下方面 参数检查不能优化或者遗漏的查表 Method.invoke本身耗时
初次学习JVM时,不建议去看Android Art、Hotspot等重量级JVM的实现,它内部的防御代码很多,还有android与libcore、bionic库紧密耦合,以及分层、内联甚至能把编译器的语义分析绕进去,因此找一个教学用的、嵌入式小型的JVM有利于节约自己的时间。因为以前折腾过OpenWrt,听过有大神推荐过jamvm,只有不到200个源文件,非常适合学习。
在工具的选择上,个人推荐SourceInsight。对比了好几个工具clion,vscode,sublime,sourceinsight,只有sourceinsight对索引、符号表的解析最准确。
参考这里
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,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。
在Stackoverflow上认为反射比较慢的程序员主要有如下看法
验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证
产生很多临时对象,造成GC与计算时间消耗
由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)
当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。
更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。
以上就是Java反射在JVM的实现 的内容,更多相关内容请关注PHP中文网(www.php.cn)!