Java 对象实例化时间 受 类字节码长度的大小的影响


看这个问题前,最好先看一下 @关于Java类加载器的代理模式的疑问 。这个问题可以说是那个问题的后续。

我做了个实验,环境为 win7 x64,sun jre7.
先整了一个简单的Sample类:

   
  public class Sample {
  
final static String C = "dfsdfasdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsadf";
void b() {
System.out.println(C);
}
}

编译后.class 文件为 588 字节。
新建5000个classloader实例,装载5000份Sample class;然后对这5000个class创建5000个实例,

   
  public class Main {
  
public static void main(String[] args) throws Exception {
String classDataRootPath = "c:\\temp";
String className = "com.test.loader.Sample";
Thread.sleep(15000);
System.out.println(new Date() + "before load class");
List<Class<?>> list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
FileSystemClassLoader fscl1 = new FileSystemClassLoader(
classDataRootPath);
list.add(fscl1.loadClass(className));
}
System.out.println(new Date() + "after load class");
Thread.sleep(5000);
Date date1=new Date();
System.out.println(date1 + "begin create instance");
List list2 = new ArrayList<>();
for (Class c : list) {
list2.add(c.newInstance());
}
Date date2=new Date();
System.out.println(date2 + "after create instance");
System.out.println("time to create instance:"+(date2.getTime()-date1.getTime()));
Thread.sleep(5000);
}
}

FileSystemClassLoader 我复用了 @关于Java类加载器的代理模式的疑问 的代码。

结果如下, classloader装载class文件用了3秒左右,创建实例用了161ms:
Fri May 24 16:43:03 CST 2013before load class
Fri May 24 16:43:06 CST 2013after load class
Fri May 24 16:43:11 CST 2013begin create instance
Fri May 24 16:43:12 CST 2013after create instance
time to create instance: 161 ms

然后我把Sample的b方法扩大,简单的加入很多打印:

   
  public class Sample {
  
final static String C = "dfsdfasdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsadf";
void b() {
System.out.println(C);System.out.println(C);System.out.println(C);System.out.println(C);
...
}
}

编译后.class 文件为 43,769 字节.
重复上面的实验:
Fri May 24 16:44:26 CST 2013before load class
Fri May 24 16:44:30 CST 2013after load class
Fri May 24 16:44:35 CST 2013begin create instance
Fri May 24 16:44:53 CST 2013after create instance
time to create instance: 18111 ms
classloader装载文件用了4秒,和上面差不多。但是 新建对象实例,omg,竟然用了18秒!谁能解释一下?

在Visual VM里查看,创建实例时, 堆和permgen 的大小 并没有任何变化。

java jvm

LMCQF 11 years, 10 months ago

发现给自己挖了一个很大的坑。。

初步分析

首先在Visual VM里看到,堆和permgen在 新建实例的时候,并没有明显的变化。但是CPU在这个阶段却是满的:
请输入图片描述

看一下 装载类之后(新建实例之前)的HeapDump的变化:
请输入图片描述

再看一下 装载类之后(新建实例之前)的HeapDump的变化:

请输入图片描述

所以classloader装载的时候只是把字节码 整到 permgen,真正 新建实例的时候, 才去解析类。
最后再来一个实验,新建5000个实例后,再新建5000个。因为第一次新建已经解析了类,第二次应该很快了。结果如下:
Fri May 24 22:00:38 CST 2013before load class
Fri May 24 22:00:42 CST 2013after load class
Fri May 24 22:00:47 CST 2013begin create instance
Fri May 24 22:01:05 CST 2013after create instance
time to create instance: 18201 ms
Fri May 24 22:01:15 CST 2013begin create instance
Fri May 24 22:01:15 CST 2013after create instance
time to create instance: 30 ms

进一步分析

在ClassLoader定义中,
protected Class<?> loadClass(String name,boolean resolve)这个方法可以指定是否解析类;而我在main方法里用到的classloader.loadClass(String name)直接调用classloder.loadClass(name,false);所以不解析类定义。

但是。。。试着直接使用classloader.loadClass(name, true)也是不行的。。。 调用到resolveClass(Class<?> c), 而Java api里写的是:This (misleadingly named) method may be used by a class loader to link a class. 。。。 the class is linked as described in the "Execution" chapter of The Java™ Language Specification.

JLS: JVM需要装载,连接和初始化一个Java类型。其中连接分为验证,准备和解析三步。 解析可以在在类 被使用的时候才开始进行:
An implementation may instead choose to resolve a symbolic reference only when
it is actively used; consistent use of this strategy for all symbolic references would represent the "laziest" form of resolution.

暗夜的眷属 answered 11 years, 10 months ago

Your Answer