类仅在用到时才会被加载,且仅加载一次。原则是延迟加载,能不加载就不加载。
类被加载的触发时机——调用类的static成员,但static的final成员若在编译时就确定值不会加载类;new对象;加载子类会先加载父类。
类加载时先根据书写顺序加载父类的静态变量与静态代码块,再根据书写顺序加载子类的静态变量与静态代码块,静态成员仅被加载一次,静态方法被动调用。
创建实例时先根据书写顺序初始化父类的非静态变量与非静态代码块,然后执行父类无参构造,再根据书写顺序初始化子类的非静态变量与非静态代码块,然后执行子类无参构造,非静态方法为被动调用。
===========================================
ClassLoader类加载器,其将类加载到jvm。java自带三个类加载器,按启动顺序分别为Bootstrap、Extension、Application,其中Bootstrap为启动类加载器,内嵌于jvm,无需加载,用c++编写,其随着jvm的启动而启动;Extension为扩展类加载器,抽象类ClassLoader的子类;Application为应用类加载器,抽象类ClassLoader的子类。
jvm的启动随着应用的启动而启动,被加载到内存,之后jvm启动Bootstrap,由其加载java的核心类库,jre/lib/下的所有jar包。Bootstrap会加载Extension和Application并创建它们的实例,然后启动Extension,由其加载jre/lib/ext/下的所有jar包,最后启动Application,由其加载classpath指定目录下的类。
java的类中有一个static的描述类结构的Class类型的实例class,该实例的初始化在类被加载到内存后,由类加载器生成返回,并赋值给class。jvm通过类的实例class,利用反射来操作该类的实例。
java程序中类的唯一标识是类的完全限定名,jvm中类的唯一标识是类的完全限定名 + 加载此类的类加载器的实例名,jvm中同一个类仅能被加载一次的同一个类用jvm中类的唯一标识来区分,由此jvm中同一个类可能被不同的类加载器加载多次。
java使用委托类加载机制,类加载器会被指定一个委托类加载器的实例。类加载器加载类时,先将其交给它的委托类加载器来加载,其中Bootstrap无委托类加载器;Extension委托Bootstrap;Application委托Extention。用户自定义类加载器的委托类加载器由构造方法传入指定,传null为Bootstrap;不传默认为Application;传一个指定的类加载器的实例。
类加载器A委托B,B默认委托Application,Appliaction委托Extension,Extension委托Bootstrap。若Bootstrao调用loadClass未找到类,返回给Extension,依次类推直到加载此类,若最后回到A也未能加载类,抛出ClassNotFoundException异常。该机制可保证最终都会委托到最底层的Bootstrap来优先加载,是java的一种安全机制,保证java的核心类库始终由Bootstrap加载,防止加载到恶意类。
类E的加载A->B->App->Ext->Boot过程中,若E最终被App加载,称App为E的定义类加载器,称A、B、App为E的初始类加载器。每个类加载器都维持一张表来记录自己作为初始类加载器所加载的类,由此A,B,App均记录了E,下次再用A,B,或App加载E时,会直接返回E的实例class,而不再加载。
java中类加载器均继承自抽象类ClassLoader,其使用loadClass方法搜索并加载类。搜索顺序为——先调用findLoaderClass方法检查要加载的类是否已经被加载过,如检查自己作为初始类加载器维护的记录表。若加载过,直接返回此类的实例class;否则返回null。若返回null,交委托类加载器加载,其过程同上。若均加载失败回到ClassLoader,其会调用findCLass()方法加载,其为ClassLoader的抽象方法,需自己实现。自定义类加载器通常用于加载网络上的类,或本地不属于本工程的类。
// 自定义类加载器,加载指定目录下指定的类
public class MyClassLoader extends ClassLoader {
// 被加载类资源的路径,路径不包括文件名
private String resPath;
// 委托类加载器
private ClassLoader parent;
// 使用默认委托类加载器的构造方法
public MyClassLoader(String resPath) {
this.resPath = resPath;
}
// 使用指定委托类加载器的构造方法
public MyClassLoader(String resPath, ClassLoader parent) {
// 基类的有参构造方法必须显示的调用
super(parent);
this.parent = parent;
this.resPath = resPath;
}
// 建议重写findClass,用于加载指定的类资源;不建议重写loadClass,其已写好搜索类的顺序
// className为完全限定名
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class<?> c = null;
// 类资源以字节数组的形式保存
byte[] classData = this.getClassData(className);
if(null == classData ) {
// 未找到类
throw new ClassNotFoundException();
} else {
// 将类资源加载到jvm,并返回实例class
c = this.defineClass(className, classData, 0, classData.length);
}
return c;
}
private byte[] getClassData(String className) {
// 流有源和目的地,输入输出流均是将源的东西送到目的地,其中输入流程序是目的地,输出流程序是源
// 输入流,接收类数组
InputStream is = null;
// 缓冲,一次最多读取的字节数
byte[] buffer = new byte[1024*4];
// 当前读取到的字节个数
int len = -1;
// 字节数组输出流,将类数据输出到字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
// 包含路径和文件名
String resUrl = resPath + "/" + className.replace(".", "/") + ".class";
URL url = new URL(resUrl);
is = url.openStream();
// 将源的内容读入到程序的缓冲字节数组
while((len = is.read(buffer)) != -1) {
// 字节数组输出流,将源的内容循环写到字节数组输出流
baos.write(buffer, 0, len);
}
// 将输出流的内容输出到字节数组并返回
return baos.toByteArray();
} catch (Exception e) {
....
} finally {
// 关闭流
....
}
return null;
}
}
// 利用应用类加载器加载指定目录下的jar
public class MyURLClassLoader {
private Method method;
// 应用类加载器为URLClassLoader的子类,使用对象上转
private static URLClassLoader ucl = (URLClassLoader) ClassLoader.getSystemClassLoader();
// 找到addURL方法,并将其返回,该方法用于加载本地jar
private Method getAddURLMethod() {
try {
// 根据方法名字和方法的参数类型返回方法对象,方法参数类型以数组的形式表示
method = URLClassLoader.class.getDeclaredMethod("addURL",
new Class[]{ URL.class });
method.setAccessible(true);
return method;
} catch (Exception e) {
....
}
return null;
}
// 将要加载的jar文件放到list中
private void loopFiles(File file, List<File> files) {
// 若file是目录,则此目录下有多个jar文件
if (file.isDirectory()) {
// 返回file目录下的所有文件及目录
File[] tmps = file.listFiles();
for (File tmp : tmps) {
// 递归继续
loopFiles(tmp, files);
}
} else {
// 若file是文件,则将后缀为jar或zip的文件添加到list
if (file.getAbsolutePath().endsWith(".jar")
|| file.getAbsolutePath().endsWith(".zip")) {
files.add(file);
}
}
}
// 加载指定的jar文件
private void loadJarFile(File file) {
try {
this.getAddURLMethod().invoke(ucl, new Object[]{ file.toURI().toURL() });
} catch (Exception e) {
....
}
}
// 加载指定路径下的全部jar文件
public void loadJarPath(String path) {
List<File> files = new ArrayList<File>();
File libPath = new File(path);
loopFiles(libPath, files);
for (File file : files) {
loadJarFile(file);
}
}
}