


Apprentissage approfondi JVM - Exemple de code du processus de fichier de classe d'analyse Java
Avant-propos :
En tant que programmeur Java, comment puis-je ne pas comprendre JVM Si vous voulez apprendre JVM, vous devez comprendre le fichier Class, Class ? est pour la machine virtuelle, tout comme un poisson pour l'eau, la machine virtuelle est vivante grâce à la classe. "Compréhension approfondie de la machine virtuelle Java" consacre un chapitre entier à expliquer les fichiers de classe, mais après l'avoir lu, je suis toujours confus et à moitié compris. Il m'est arrivé de lire un très bon livre il y a quelque temps : "Ecrivez votre propre machine virtuelle Java". L'auteur a utilisé le langage go pour implémenter une JVM simple. Bien qu'il ne réalise pas pleinement toutes les fonctions de la JVM, il est utile pour. ceux qui sont légèrement intéressés par la JVM Cela dit, la lisibilité reste très élevée. L'auteur l'explique en détail et chaque processus est divisé en un chapitre, dont une partie explique comment analyser les fichiers Class.
Ce livre n'est pas trop épais et je l'ai lu rapidement Après l'avoir lu, j'ai beaucoup gagné. Mais ce n'était qu'une question de temps avant que je l'apprenne sur papier, et je savais que je devais le faire en détail, alors j'ai essayé d'analyser le fichier de classe moi-même. Bien que le langage Go soit excellent, je ne le maîtrise finalement pas, surtout si je ne suis pas habitué à sa syntaxe consistant à mettre des types après des variables, je ferais donc mieux de m'en tenir à Java.
Fichier de classe
Qu'est-ce qu'un fichier de classe ?
La raison pour laquelle Java peut atteindre plusieurs plates-formes est que sa phase de compilation ne compile pas directement le code dans un langage machine lié à la plate-forme, mais le compile d'abord sous forme binaire de bytecode Java et le place dans le fichier de classe. , la machine virtuelle charge ensuite le fichier Class et analyse le contenu requis pour exécuter le programme. Chaque classe sera compilée dans un fichier de classe distinct et la classe interne sera également utilisée comme classe indépendante pour générer sa propre classe.
Structure de base
Trouvez simplement un fichier de classe et ouvrez-le avec Sublime Text comme ceci :
Êtes-vous confus ? Le format de base des fichiers de classe est donné dans la spécification de la machine virtuelle Java. Il vous suffit de l'analyser selon ce format :
ClassFile { u4 magic; 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]; }
Les types de champs dans ClassFile sont u1, u2 et u4. ça ? Du drap de laine ? En fait, c'est très simple, cela signifie respectivement 1 octet, 2 octets et 4 octets. Les quatre premiers octets de
sont : Magic, qui est utilisé pour identifier de manière unique le format de fichier. Il est généralement appelé le nombre magique afin que la machine virtuelle puisse reconnaître le fichier chargé. c'est au format classe, le nombre magique de fichiers de classe est cafébabe. Pas seulement les fichiers de classe, la plupart des fichiers ont un numéro magique pour identifier leur format.
La partie suivante contient principalement des informations sur le fichier de classe, telles que le pool de constantes, les indicateurs d'accès à la classe, la classe parent, les informations d'interface, les champs, les méthodes, etc. Pour des informations spécifiques, veuillez vous référer à « Machine virtuelle Java Spécification".
Analyse
Type de champ
Comme mentionné ci-dessus, les types de champs dans ClassFile sont u1, u2, u4, qui représentent respectivement 1 octet, 2 octets et 4 An non signé. nombre entier d'octets. En Java, short, int et long sont des entiers signés de 2, 4 et 8 octets respectivement. Sans le bit de signe, ils peuvent être utilisés pour représenter u1, u2 et u4.
public class U1 { public static short read(InputStream inputStream) { byte[] bytes = new byte[1]; try { inputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } short value = (short) (bytes[0] & 0xFF); return value; } } public class U2 { public static int read(InputStream inputStream) { byte[] bytes = new byte[2]; try { inputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } int num = 0; for (int i= 0; i < bytes.length; i++) { num <<= 8; num |= (bytes[i] & 0xff); } return num; } } public class U4 { public static long read(InputStream inputStream) { byte[] bytes = new byte[4]; try { inputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } long num = 0; for (int i= 0; i < bytes.length; i++) { num <<= 8; num |= (bytes[i] & 0xff); } return num; } }
Piscine constante
Après avoir défini le type de champ, nous pouvons lire le fichier de classe. Tout d'abord, nous devons lire les informations de base telles que les nombres magiques. Cette partie est très simple :
FileInputStream inputStream = new FileInputStream(file); ClassFile classFile = new ClassFile(); classFile.magic = U4.read(inputStream); classFile.minorVersion = U2.read(inputStream); classFile.majorVersion = U2.read(inputStream);
Cette partie n'est qu'un échauffement, la prochaine grande partie est la piscine constante. Avant d’analyser le pool constant, expliquons d’abord ce qu’est le pool constant.
Le pool de constantes, comme son nom l'indique, est un pool de ressources qui stocke des constantes. Les constantes font ici référence à des littéraux et des références de symboles. Les littéraux font référence à certaines ressources chaîne , et les références de symboles sont divisées en trois catégories : les références de symboles de classe, les références de symboles de méthode et les références de symboles de champ. En plaçant des ressources dans le pool constant, d'autres éléments peuvent être directement définis comme index dans le pool constant, évitant ainsi le gaspillage d'espace. Il ne s'agit pas seulement du fichier de classe, mais aussi du fichier exécutable Android dex. les ressources, etc. sont placées dans DexData, et d'autres éléments localisent les ressources via des index. La spécification de la machine virtuelle Java donne le format de chaque élément du pool constant :
cp_info { u1 tag; u1 info[]; }
Le format ci-dessus est juste un format général. Il existe 14 formats de données réellement contenus dans le pool constant, chaque format étant le suivant. les valeurs des balises sont différentes, comme indiqué ci-dessous :
Comme il y a trop de formats, seule une partie de l'article est sélectionnée pour expliquer :
Ici, nous lisons d'abord la taille du pool constant. , initialisons le pool constant :
//解析常量池 int constant_pool_count = U2.read(inputStream); ConstantPool constantPool = new ConstantPool(constant_pool_count); constantPool.read(inputStream);
Ensuite, lisez chaque élément un par un et stockez-le dans le tableau cpInfo dont vous avez besoin. il convient de noter ici que l'indice cpInfo[] commence à partir de 1. Initialement, 0 n'est pas valide et la taille réelle du pool constant est constant_pool_count-1.
public class ConstantPool { public int constant_pool_count; public ConstantInfo[] cpInfo; public ConstantPool(int count) { constant_pool_count = count; cpInfo = new ConstantInfo[constant_pool_count]; } public void read(InputStream inputStream) { for (int i = 1; i < constant_pool_count; i++) { short tag = U1.read(inputStream); ConstantInfo constantInfo = ConstantInfo.getConstantInfo(tag); constantInfo.read(inputStream); cpInfo[i] = constantInfo; if (tag == ConstantInfo.CONSTANT_Double || tag == ConstantInfo.CONSTANT_Long) { i++; } } } }
Jetons d'abord un coup d'œil au format CONSTANT_Utf8. Cet élément stocke une chaîne codée en MUTF-8 :
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
Alors, comment lire cet élément ?
public class ConstantUtf8 extends ConstantInfo { public String value; @Override public void read(InputStream inputStream) { int length = U2.read(inputStream); byte[] bytes = new byte[length]; try { inputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } try { value = readUtf8(bytes); } catch (UTFDataFormatException e) { e.printStackTrace(); } } private String readUtf8(byte[] bytearr) throws UTFDataFormatException { //copy from java.io.DataInputStream.readUTF() } }
很简单,首先读取这一项的字节数组长度,接着调用readUtf8(),将字节数组转化为String字符串。
再来看看CONSTANT_Class这一项,这一项存储的是类或者接口的符号引用:
CONSTANT_Class_info { u1 tag; u2 name_index; }
注意这里的name_index并不是直接的字符串,而是指向常量池中cpInfo数组的name_index项,且cpInfo[name_index]一定是CONSTANT_Utf8格式。
public class ConstantClass extends ConstantInfo { public int nameIndex; @Override public void read(InputStream inputStream) { nameIndex = U2.read(inputStream); } }
常量池解析完毕后,就可以供后面的数据使用了,比方说ClassFile中的this_class指向的就是常量池中格式为CONSTANT_Class的某一项,那么我们就可以读取出类名:
int classIndex = U2.read(inputStream); ConstantClass clazz = (ConstantClass) constantPool.cpInfo[classIndex]; ConstantUtf8 className = (ConstantUtf8) constantPool.cpInfo[clazz.nameIndex]; classFile.className = className.value; System.out.print("classname:" + classFile.className + "\n");
字节码指令
解析常量池之后还需要接着解析一些类信息,如父类、接口类、字段等,但是相信大家最好奇的还是java指令的存储,大家都知道,我们平时写的java代码会被编译成java字节码,那么这些字节码到底存储在哪呢?别急,讲解指令之前,我们先来了解下ClassFile中的method_info,其格式如下:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
method_info里主要是一些方法信息:如访问标志、方法名索引、方法描述符索引及属性数组。这里要强调的是属性数组,因为字节码指令就存储在这个属性数组里。属性有很多种,比如说异常表就是一个属性,而存储字节码指令的属性为CODE属性,看这名字也知道是用来存储代码的了。属性的通用格式为:
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
根据attribute_name_index可以从常量池中拿到属性名,再根据属性名就可以判断属性种类了。
Code属性的具体格式为:
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
其中code数组里存储就是字节码指令,那么如何解析呢?每条指令在code[]中都是一个字节,我们平时javap命令反编译看到的指令其实是助记符,只是方便阅读字节码使用的,jvm有一张字节码与助记符的对照表,根据对照表,就可以将指令翻译为可读的助记符了。这里我也是在网上随便找了一个对照表,保存到本地txt文件中,并在使用时解析成HashMap。代码很简单,就不贴了,可以参考我代码中InstructionTable.java。
接下来我们就可以解析字节码了:
for (int j = 0; j < methodInfo.attributesCount; j++) { if (methodInfo.attributes[j] instanceof CodeAttribute) { CodeAttribute codeAttribute = (CodeAttribute) methodInfo.attributes[j]; for (int m = 0; m < codeAttribute.codeLength; m++) { short code = codeAttribute.code[m]; System.out.print(InstructionTable.getInstruction(code) + "\n"); } } }
运行
整个项目终于写完了,接下来就来看看效果如何,随便找一个class文件解析运行:
哈哈,是不是很赞!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

Guide du générateur de nombres aléatoires en Java. Nous discutons ici des fonctions en Java avec des exemples et de deux générateurs différents avec d'autres exemples.

Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est

Guide de TimeStamp to Date en Java. Ici, nous discutons également de l'introduction et de la façon de convertir l'horodatage en date en Java avec des exemples.

Les capsules sont des figures géométriques tridimensionnelles, composées d'un cylindre et d'un hémisphère aux deux extrémités. Le volume de la capsule peut être calculé en ajoutant le volume du cylindre et le volume de l'hémisphère aux deux extrémités. Ce tutoriel discutera de la façon de calculer le volume d'une capsule donnée en Java en utilisant différentes méthodes. Formule de volume de capsule La formule du volume de la capsule est la suivante: Volume de capsule = volume cylindrique volume de deux hémisphères volume dans, R: Le rayon de l'hémisphère. H: La hauteur du cylindre (à l'exclusion de l'hémisphère). Exemple 1 entrer Rayon = 5 unités Hauteur = 10 unités Sortir Volume = 1570,8 unités cubes expliquer Calculer le volume à l'aide de la formule: Volume = π × r2 × h (4
