17 arr2[j] = arr1[j];
18 }
19 elapse = System.currentTimeMillis() - start;
20 System.out.println("The elapse for normal copy is " + elapse + " ms");
21 }
22 /* 输出结果如下:
23 The elapse for System.arraycopy is 187 ms
24 The elapse for normal copy is 297 ms
25 */
复制代码
3. 类加载器:ClassLoader
Java程序中所有的class都是通过JVM的类加载器机制来进行加载、链接和解析并最终生成本地的可执行码后再被执行的。每个Java的程序都至少拥有三个类加载器:
1) 引导类加载器(bootstrap classlClassLoader) 引导类加载负责加载系统类,如rt.jar等,它是JVM整体中的一部分,通常都是用c语言来实现的,引导类加载器没有对应的ClassLoader对象,因此当执行String.class.getClassLoader()时,将返回null。
2) 扩展类加载器(extension classlClassLoader)
它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。
3) 系统类加载器(system classloader)
它负责在JVM被启动时,加载CLASSPATH所指定的JAR类包和类路径。通过ClassLoader.getSystemClassLoader()方法可以找到该类加载器。
以上三个类加载器之间存在一种父子关系,需要明确说明的是这种父子关系并非继承链上的超类和子类之间的关系,而是ClassLoader内部都维护着一个指向其parent类加载器的对象引用,这种关系有些类似于树中子节点和父节点之间的关系。因此在得到system classloader的之后,再想获得extension classlClassLoader就非常容易了,如调用ClassLoader.getSystemClassLoader().getParent()方法将返回扩展类加载器的对象引用,如果继续调用扩展类加载器的getParent()方法,如ClassLoader.getSystemClassLoader().getParent().getParent(),原本该调用之后应该返回引导类加载器(bootstrap classlClassLoader)的对象引用,然而由于该类并非Java对象,因此该调用将返回null。由于String是由引导类加载器来加载的,我们也可以通过以下调用来验证这一结果。
1 public static void main(String[] args) {
2 System.out.println(String.class.getClassLoader());
3 }
4 /* 输出结果为:
5 null
6 */
复制代码
对于我们自定义的ClassLoader,如果没有明确的指定,其确实的parent将指向系统类加载器(system classloader)。
ClassLoader加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个ClassLoader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个ClassLoader负责载入,除非是显式的使用另外一个ClassLoader载入;委托机制则是先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。
每个ClassLoader加载Class的过程是:
1) 检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2) 如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3) 请求parent classloader载入,如果成功到8,不成功到5
4) 请求jvm从bootstrap classloader中载入,如果成功到8
5) 寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6) 从文件中载入Class,到8.
7) 抛出ClassNotFoundException.
8) 返回Class.
事实上对于绝大多数的应用场景,我们均不需要实现自定义的ClassLoader,Java中已有的三个ClassLoader已经足够完成几乎所有的工作了,那么自定义ClassLoader的应用场合又是什么呢?这里可以给出一个常用的case。众所周知,Java编译后的.class很容易被反编译工具翻译回原有的.java文件,这样便会给系统带来一些潜在的风险和不安定因素,出于安全方面考虑,我们需要将编译后的.class文件进一步加密,而加密后的文件,是无法通过JVM自带的三个ClassLoader完成正常加载工作的,因此这里就需要我们实现自定义的ClassLoader,该加载器将会在读入加密的.class文件之后,在内存中进行解密,再将解密后的字节码交给JVM去继续执行。这里需要提醒注意的是,对于需要动态加载的普通插件jar包,我们并不需要通过自定义的ClassLoader来完成其加载工作,而是直接使用URLClassLoader即可,见如下代码:
1 public void testLoader() {
2 URL u = new URL("file:///path/plugin.jar");
3 URLClassLoader pluginLoader = new URLClassLoader(new URL[] { u });
4 Class< > c = pluginLoader.loadClass("mypackage.MyClass");
5 }
复制代码
对于自定义的类加载器类,我们需要扩展自ClassLoader,按照JDK的推荐,我们只需在子类中重载findClass方法。在通过父类加载器检查所请求的类后,此方法将被loadClass方法调用。以下的示例代码将会在读入.class文件之后模拟解密的操作,再将解密后的字节码作为参数交给ClassLoader.de