精华内容
下载资源
问答
  • Dalvik 分析 - Class加载

    万次阅读 2010-07-06 10:17:00
    Java 源代码经过编译后会生成后缀为class的文件,也即字节码文件。然后在Android中使用dx工具将其转换为后缀为jar 的dex类型文件。Dalvik 虚拟机负责解释并执行编译...本文从dexfile 文件分析及Class加载中的数据结构入

    Java 源代码经过编译后会生成后缀为class的文件,也即字节码文件。然后在Android中使用dx工具将其转换为后缀为jar dex类型文件。Dalvik 虚拟机负责解释并执行编译后的字节码。在解释执行字节码之前,当然要读取文件,分析文件的内容,得到字节码,然后才能解释执行之。在整个的加载过程中,最为重要的就是对Class的加载 – Class包含MethodMethod 又包含code。通过对Class的加载,我们即可获得所需执行的字节码。

     

    本文从dexfile 文件分析及Class加载中的数据结构入手,结合主要流程,对整个加载过程进行分析,期望对大家有所帮助。

     

     

    1. DexFile 在内存中的映射

     

    Android 系统中,java源文件会被编译为后缀为jardex类型文件,在代码中称为dexfile。在加载Class之前,必先读取相应的jar文件。通常我们使用read函数来读取文件中的内容。但在Dalvik中使用mmap函数,和read不同,mmap函数会将dex文件映射到内存中,这样通过普通的内存读取操作即可访问dex file 中的内容。

     

    Dexfile 的文件格式如下图所示,主要有三部分组成:头部,索引,数据。通过头部可知索引的位置和数目,可知数据区的起始位置。其中classDefsOff指定了ClassDef在文件的起始位置,dataOff指定了数据在文件的起始位置,ClassDef即可理解为Class的索引。通过读取ClassDef可获知Class的基本信息,其中classDataOff指定了Class数据在数据区的位置。

     

    在将dexfile文件映射到内存后,即会调用dexFileParse函数对其分析,分析的结果存放于名为DexFile 的数据结构中。DexFile中的baseAddr指向映射区的起始位置,pClassDefs指向ClassDefs(即class索引)的起始位置。由于在查找class时,都是使用class的名字进行查找,为了加快查找速度,创建了一个hash表。在hash表中对class名字进行hash,并生成index。这些操作都是在对文件解析时所完成的,这样虽然在加载过程中比较耗时,但是在运行过程中确可节省大量查找时间。

     

     

     

     

     

     

    2. ClassObject -  Class在加载后的表现形式

     

    在对文件解析完成后就要加载Class的具体内容了!在Dalvik中,由ClassObject 这个数据结构负责存放加载的信息。如下图所示,加载过程会在内存中alloc几个区域,分别存放directMethods, virtualMethods, sfields, ifields。这些信息正是从dex 文件的数据区中读取。首先会读取Class的详细信息,从中获知directMethod, virtualMethod, sfield, ifield等的信息,然后再读取。下图为加载完成后的示意。 这里并未介绍加载的每个细节,感兴趣的同学可通过此二图自行分析。

    还请大家注意的是在ClassObject结构中有个名为super的成员。通过super成员来指向它的超类。

     

     

     

    3. findClassNoInit – 负责加载Class并生成相应ClassObject的函数。

     

    第二节介绍了加载后的数据结构,本节会分析负责加载工作的函数- findClassNoInit 请注意在获取Class索引时,会分为基本类库文件和用户类文件两种情况。在Dalvik分析之准备篇中,grund.sh中有一语句   export BOOTCLASSPATH=$bootpath/core.jar:$bootpath/ext.jar:$bootpath/framework.jar:$bootpath/android.police.jar” 这条语句指定了Dalvik所需的基本库文件,如果没有此语句,Dalvik在启动过程中就会报错退出。

     

    LoadClassFromDex 函数先会读取Class的具体数据(从ClassDataoff处),然后按图索骥,分别加载 directMethod virtualMethodifieldsfield

     

    作为业界TOP公司出品的东东,当然要关注执行效率了^_^。首先,在加载后我们要将其缓存起来,以便以后使用方便。其次,在查找过程中,如果我们顺序查找的话,当然是很慢的拉。这当然是俺们senior engineer所不能容忍的,所以gDvm.loadedClasses这个Hash表就隆重出场了。什么,这位同学说没有几个Class,至于这么大动干戈么。让我们看看,通过在准备篇中介绍的方法,我们在main.c 249行设置断点,这个时候基本库已加载完毕。当程序停下时,我们看看gDvm的值,可以看到numLoadedClasses这个成员的值为212 !也即意味着这个时候我们什么也没做,用户类也未加载,Dalvik 已加载的Class数目已达到212

     

    dvmLinkClass 好长,但是其最终好像会再次调用findClassNoInit。嗯,也是可以理解的嘛。如果一个子类需要调用超类的函数,那它当然要先加载超类了,可能的话,甚至会加载超类的超类^_^  

     

     

     

     

    眼见为虚,实践出真知,拿gdb调试下。

    findClassNoInit函数处设置断点(gdb提示符后输入”b findClassNoInit”),在gdb提示符后连续几次执行”c””bt”。可出现如下信息,可以看到在函数调用栈上可以多次看到findClassNoInit的身影。

     

    (gdb) bt

    #0  findClassNoInit (descriptor=0xfef4c7f4 "??????%", loader=0x0, pDvmDex=0x0)

        at dalvik/vm/oo/Class.c:1373

    #1  0xf6fc4d53 in dvmFindClassNoInit (descriptor=0xf5046a63 "Ljava/lang/Object;", loader=0x0)

        at dalvik/vm/oo/Class.c:1194

    #2  0xf6fc6c0a in dvmResolveClass (referrer=0xf5837400, classIdx=290,

        fromUnverifiedConstant=false) at dalvik/vm/oo/Resolve.c:94

    #3  0xf6fc3476 in dvmLinkClass (clazz=0xf5837400, classesResolved=false)

        at dalvik/vm/oo/Class.c:2537

    #4  0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff0df6 "Ljava/lang/Class;", loader=0x0,

        pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489

     

     

    现在从另一个角度去观察。在class.c 2575行设置断点,然后等待程序停下。看下clazz的内容。

    (gdb) p clazz->super->descriptor

    $6 = 0xf5046a63 "Ljava/lang/Object;"

    (gdb) p clazz->descriptor

    $7 = 0xf5046121 "Ljava/lang/Class;"

     

     

    4. 基本类库文件的加载

     

    先在findClassNoInit函数处设置断点,然后运行程序,等待程序的停下。

     

    (gdb) b findClassNoInit

    Breakpoint 2 at 0xf6fc13e0: file dalvik/vm/oo/Class.c, line 1373.

    (gdb) c

    Continuing.

     

    看看谁是第一个加载的Class,及其调用关系。

     

    (gdb) bt

    #0  findClassNoInit (descriptor=0x0, loader=0x0, pDvmDex=0x0) at dalvik/vm/oo/Class.c:1373

    #1  0xf6fc32a1 in dvmLinkClass (clazz=0xf5837350, classesResolved=false)

        at dalvik/vm/oo/Class.c:2491

    #2  0xf6fc1b67 in findClassNoInit (descriptor=0xf6ff1ded "Ljava/lang/Thread;", loader=0x0,

        pDvmDex=0xa04c720) at dalvik/vm/oo/Class.c:1489

    #3  0xf6f92692 in dvmThreadObjStartup () at dalvik/vm/Thread.c:328

    #4  0xf6f800e6 in dvmStartup (argc=2, argv=0xa041190, ignoreUnrecognized=false, pEnv=0xa0411a0)

        at dalvik/vm/Init.c:1155

    #5  0xf6f8b8e3 in JNI_CreateJavaVM (p_vm=0xf6ff0df6, p_env=0xf6ff0df6, vm_args=0xfef4d0b0)

        at dalvik/vm/Jni.c:4198

    #6  0x08048893 in main (argc=3, argv=0xfef4d168) at dalvik/dalvikvm/Main.c:212

     

    函数调用顺序清晰可见:main -> JNI_CreateJavaVM-> dvmStartup-> dvmThreadObjStartup-> dvmFindSystemClassNoInit-> findClassNoInit 观察仔细的同学可能会问在调用栈中没有看到dvmFindSystemClassNoInit啊,为何你这样写啊?我估计编译器将其作为inline优化了,导致gdb看不到有dvmFindSystemClassNoInit的栈。从回溯栈中也可清晰的看到

     

     

    5. 用户类文件的加载

    用户类文件的加载颇为曲折,它会先加载一个Class。然后这个Class去负责用户类文件的加载,当然了,这个Class又会通过JNI的方式去曲折调用到findClassNoInit。这个就留给大家自己分析了, 其实楼主自己也不是很明白为什么要这么大费周折^_^

     

     

     

     

     

     

     

    展开全文
  • Tomcat的class加载的优先顺序一览

    千次阅读 2013-01-16 14:33:52
    Tomcat的class加载的优先顺序一览 1.最先是$JAVA_HOME/jre/lib/ext/下的jar文件。 2.环境变量CLASSPATH中的jar和class文件。 3.$CATALINA_HOME/common/classes下的class文件。 4.$CATALINA_HOME/commons...


    Tomcat的class加载的优先顺序一览

    1.最先是$JAVA_HOME/jre/lib/ext/下的jar文件。

    2.环境变量CLASSPATH中的jar和class文件。

    3.$CATALINA_HOME/common/classes下的class文件。

    4.$CATALINA_HOME/commons/endorsed下的jar文件。

    5.$CATALINA_HOME/commons/i18n下的jar文件。

    6.$CATALINA_HOME/common/lib 下的jar文件。
    (JDBC驱动之类的jar文件可以放在这里,这样就可以避免在server.xml配置好数据源却出现找不到JDBC Driver的情况。)
    7.$CATALINA_HOME/server/classes下的class文件。

    8.$CATALINA_HOME/server/lib/下的jar文件。

    9.$CATALINA_BASE/shared/classes 下的class文件。

    10.$CATALINA_BASE/shared/lib下的jar文件。

    11.各自具体的webapp /WEB-INF/classes下的class文件。

    12.各自具体的webapp /WEB-INF/lib下的jar文件。

    class的搜寻顺序如下:
    -------------
    Bootstrap classes of your JVM 
    System class loader classses (described above) 
    /WEB-INF/classes of your web application 
    /WEB-INF/lib/*.jar of your web application 
    $CATALINA_HOME/common/classes 
    $CATALINA_HOME/common/endorsed/*.jar 
    $CATALINA_HOME/common/i18n/*.jar 
    $CATALINA_HOME/common/lib/*.jar 
    $CATALINA_BASE/shared/classes 
    $CATALINA_BASE/shared/lib/*.jar 
    --------------


    因此放在不同webapp里的class文件,会被classloader加载成不同的实例。
    例如假设下面两个不同内容的class。分别放在不同的webapp的class目录下。

    package com.lizongbo;
    public class TestClass {
      private String NAME="lizongbo";
    }

    package com.lizongbo;
    public class TestClass {
      private String NAME="li_zongbo";
    }

    在不同的webapp得到的com.lizongbo.NAME结果是不同的,且互不影响。

    但是注意,以下包名开头的class例外:
    javax.* 
    org.xml.sax.* 
    org.w3c.dom.* 
    org.apache.xerces.* 
    org.apache.xalan.* 

    ps,注意.在各个jar中的\META-INF\MAINFEST.MF文件里Class-Path键值对,也会提供jar的加载优先顺序。
    例如某jar的MAINFEST.MF内容如下:
    Manifest-Version: 1.0
    Created-By: lizongbo
    Class-Path: commons-beanutils.jar
    Class-Path: commons-collections.jar
    Class-Path: commons-dbcp.jar
    Class-Path: commons-digester.jar
    Class-Path: commons-logging.jar
    Class-Path: commons-pool.jar
    Class-Path: commons-services.jar
    Class-Path: commons-validator.jar
    Class-Path: jakarta-oro.jar
    Main-Class: com.lizongbo.MyTestClass


    那么在加载这个jar的时候,会先在此jar所在目录下依次先加载commons-beanutils.jar,commons-collections.jar。。。等jar文件。

    在不同的地方放置jar和class可能会产生意想不到的后果,,尤其是不同版本的jar文件,因此在实际应用部署web应用时候要特别留心.


    例如 使用javamail常见的一个出错信息:
    javax.mail.NoSuchProviderException: No provider for smtp
    其真实原因就很可能如下:
    在不同的加载jar的目录下放置了不同版本的mail.jar,比如一个是javamail1.3.1的mail.jar
    在D:\jakarta-tomcat-5.5.8\common\lib下,而另外一个是javamail1.3.2的mail.jar在
    D:\jakarta-tomcat-5.5.8\webapps\lizongbo\WEB-INF/lib下,
    那么lizongbo这个webapp中使用到javamail进行邮件发送的时候,便会出现No provider for smtp的错误。
    展开全文
  • 从下面代码可以看出来几点:1、 class文件的加载的时机显示加载: 调用ClassLoader.loadClass(className)与Class.forName(className)隐式加载: 创建类对象 使用类的静态域 创建子类对象 使用子类的静态域 2、...

    转载请注明出处:jiq•钦's technical Blog


    从下面代码可以看出来几点:


    1、 class文件的加载的时机

    显示加载:

             调用ClassLoader.loadClass(className)与Class.forName(className)

    隐式加载:

             创建类对象

             使用类的静态域

             创建子类对象

             使用子类的静态域

    注意其实还有其他特殊的隐式加载:

    在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class

    在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class

    在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件。

     

    2、 两种显示加载class文件到JVM的区别

    Class.forName(className)加载class的同时会初始化静态域

    ClassLoader.loadClass(className)则不会初始化静态域

    Class.forName借助当前调用者的class的ClassLoader完成class的加载。

     

    3、 class文件的隐式加载会执行static域 

    4、 JVM默认的ClassLoader是AppClassLoader 

    =================================== 下面附上测试代码 =========================================

    package jiq.basic;
    
    class SuperClass
    {
    	public static int i = 0;
    	public static void func(){}
    	static {System.out.println("静态区域被执行!");}
    }
    
    class SubClass extends SuperClass
    {
    	public static int j = 0;
    }
    
    public class Test 
    {
    	//仅仅只是作为参数或者变量出现不会载入对应的class文件
    	SuperClass cc1 = null;
    	
    	public static void TestClassLoader(SuperClass vv) throws ClassNotFoundException
    	{
    		/**
    		 * 显示载入class并初始化静态域
    		 * 将打印:静态区域被执行!
    		 */
    		Class.forName("jiq.basic.SuperClass");
    		
    		/**
    		 * 隐式载入class并初始化静态域
    		 * 将打印:静态区域被执行!
    		 */
    		SuperClass c = new SuperClass();	//使用关键字new创建对象
    		SuperClass.i = 9;	//使用类的静态字段
    		SuperClass.func(); //使用类的静态方法
    		new SubClass();	//使用关键字new创建子类对象
    		SubClass.j = 10; //使用子类的静态域
    		
    		/**
    		 * 仅仅只是作为参数或者变量出现不会载入对应的class文件
    		 * 不会打印:静态区域被执行!
    		 */
    		SuperClass cc = vv;
    		SuperClass array[] = new SuperClass[5];
    		
    		/**
    		 * 调用ClassLoader的loadClass方法只会加载class文件到JVM
    		 * 并不会初始化静态区域,这就是ClassLoader.loadClass与Class.forName的区别
    		 * 不会打印:静态区域被执行!
    		 */
    		ClassLoader.getSystemClassLoader().loadClass("jiq.basic.SuperClass");
    				
    		/**
    		 * 输出为sun.misc.Launcher$AppClassLoader@2d74e4b3
    		 * 可以看出JVM默认的类加载器ClassLoader是AppClassLoader
    		 * 而BootStrapClassLoader和ExtClassLoader都是相对较为特殊的ClassLoader
    		 */
    		System.out.println(ClassLoader.getSystemClassLoader());
    	}
    	
    	public static void main(String[] args) throws ClassNotFoundException 
    	{
    		TestClassLoader(null);
    	}
    
    }
    


    展开全文
  • 加载->链接(验证+准备+解析)->初始化(使用...最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。 (2)链接:  验证:确保被加载类的正确性;

    加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
    (1)加载
      首先通过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据。
    (2)链接:
      验证:确保被加载类的正确性;
      准备:为类的静态变量分配内存,并将其初始化为默认值;
      解析:把类中的符号引用转换为直接引用; (区别见下面)
    (3)为类的静态变量赋予正确的初始值
    3、类的初始化

    在JVM中,类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。而解析阶段即是虚拟机将常量池内的符号引用替换为直接引用的过程。
    1.符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

    2.直接引用:
    直接引用可以是
    (1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)
    (2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)
    (3)一个能间接定位到目标的句柄
    直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了

    展开全文
  • )V的格式,而BClass是优先加载jarB(version:2.0)中的BClass。 这里得出两个结论 1、对于 同包名同类名的类, 类加载器会加载引用链最短的jar包内的类 2、编译器在方法调用前就完全确定一个方法的一一...
  • java加密class和解密class加载运行

    千次阅读 2012-06-11 14:49:18
    /** 继承系统默认的类加载器,重写方法 findClass(String ) 默认抛出 异常ClassNotFoundException异常 **/ public class MyClassLoader extends ClassLoader { public static final int CLASS_LOADER_CHAR = 0X12...
  • Class Loading Linking Initializing:编译 加载 初始化 这节课,我们讲 class 是怎么从硬盘中加载到内存中,并且准备执行的。 package com.mashibing.jvm.c2_classloader; public class T004_ParentAndChild { ...
  • class Tester{ static{ System.out.println("Tester类的静态初始化块!"); } } public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException {
  • Class加载过程与类加载

    千次阅读 2020-05-02 19:03:55
    在说类加载器和双亲委派模型之前,先来梳理下Class类文件的加载过程,JAVA虚拟机为了保证 实现语言的无关性,是将虚拟机只与“Class 文件”字节码这种特定形式的二进制文件格式相关联,而不是与实现语言绑定。...
  • 加载机制及类加载加载Class流程

    千次阅读 2017-06-08 17:00:52
    先让父类加载器试图加载Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。缓存机制。缓存机制保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该
  • class文件的加载过程

    千次阅读 2019-01-03 14:33:17
    1、在加载class文件的时候,JVM会先加载类中的所有静态成员( 方法,变量,静态代码块 )都加载到方法区class文件的所处静态区中 2、当把所有的静态成员加载完成之后,开始给类中的所有静态成员变量进行默认初始化 3...
  • Class加载器,内部类加载实验

    千次阅读 2015-11-21 15:03:19
    最近实验通过jdk编译后的Class多出一个内部类class文件,美元$符号的class文件,如A.class ,A$B.class加载A.class总是报错。 除了加载内部类方法,还有就是把class文件打包成jar,通过自定义URLClassLoader 加载...
  • classLoader类加载器如何加载class

    千次阅读 2018-05-05 11:54:35
    ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说...但是,jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。想想也是的,一次性加载那么多j...
  • 如何加载class文件

    千次阅读 2018-04-24 14:16:03
    上一篇ClassLoader的基础知识中提到,类加载器第一个作用就是加载Class到JVM中,其实就是加载jar文件中的.class文件和javac编译.java生成的.class文件。ClassLoader加载一个class文件到JVM时需要经过以下几个步骤:...
  • [Java]远程/网络加载class

    万次阅读 2021-03-04 18:45:28
    加载(ClassLoader)入口提供 protected final Class<?> defineClass(String name, byte[] b, int off, int len) 大致的意思就是通过字节码文件加载,知道了这个方法就可以实现:能拿到字节码就能加载~ ...
  • java Class加载的时机

    千次阅读 2016-12-29 10:29:53
    简单概括:当主动使用此类时,就会被加载,分为以下6种...反射(Class.forName()) 5.初始化类的子类 6.java虚拟机启动时的main方法所在类下面用代码来举例: 首先要明白的是,类被加载判断:类中的静态代码块会
  • Xposed hook动态加载class

    万次阅读 2018-06-07 14:04:49
    问题根源是ClassLoader不对,因为那几个class是动态加载进来的。通过先ClassLoader.loadCLass则可以很轻松的拿到想要的classLoader对象 XposedBridge.hookAllMethods(ClassLoader.class, "loadClass", new...
  • 本文主要详细介绍了Java的Class(类)加载器的概念以及类加载机制。
  • java如何加载class文件

    千次阅读 2019-06-13 21:51:01
    jvm将描述类的数据从class文件加载到内存,然后对数据进行校验,解析以及初始化,在java虚拟机中形成可以直接使用的java类型。 java类文件的加载是动态的。 java为我们提供了2种类文件加载的动态机制。 1.隐式...
  • 1、加载:通过类的全限定名来获取定义此类的二进制流;在内存中生成一个Class对象,作为方法区该类各种数据的访问入口。 获取方式: ①从jar、war包中获取; ②从网络中获取; ③运行时计算机生成:Applet。 2、链接...
  • findClass()用于写类加载逻辑、loadClass()方法的逻辑里如果父类加载加载失败则会调用自己的findClass()方法完成加载,保证了双亲委派规则。 1、如果不想打破双亲委派模型,那么只需要重写findClass方法...
  • JVM加载class文件的原理机制

    千次阅读 2016-06-19 21:30:19
    Java语言是一种具有动态性的解释型语言,类(class)只有被加载到JVM后才能运行。当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序。这个加载...
  • Class文件的加载过程

    千次阅读 2014-03-31 12:29:56
    class文件中描述的各种信息,最终都需要被加载到虚拟机中之后,才能被运行和使用。 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的java...
  • JVM 加载class流程

    千次阅读 2018-08-05 11:02:19
    JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。  由于Java 的跨平台性,经过编译的Java 源程序并不是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,482,441
精华内容 592,976
关键字:

class加载