- JVM和类的关系
- 类加载和类初始化
1,类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。也就是说,当程序中使用任何类的时候,系统都会为之建立一个java.lang.Class对象。通过使用不同的类加载器,可以从不同来源加载类的2进制数据,比如说本地系统,jar包中,或者网络中。
注意一点:程序不一定是说非要等到使用这个类的时候,才会去加载这个该类,也可以T前加载进去的。
2,类的连接:把类的2进制数据合并到jar中。当类被加载之后,系统会为之生成一个对应的Class对象,这时开始进入连接阶段。类的连接又可以分为3个阶段:验证,准备,解析。
3,类的初始化:对类的静态属性进行初始化。这里包含有2个钟方式: 第一种是申明一个属性的时候就指定初始值,第二种是静态初始化块为静态属性指定初始值。关于这一点在前面的另外一篇博客(类初始化)里面有详细介绍,这里简单总结下:要是2次都没有赋值的话,静态属性有默认值的,从上到下依次执行,每次都要先走父类,然后才能到自己,最终静态初始化块只执行一次,这个要和普通初始化块区分开来的。
- ClassLoader
package tz.web.main; import java.util.*; import java.net.*; import java.io.*; public class ClassLoaderPropTest { public static void main(String[] args) throws IOException { // 获取系统类加载器 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器:" + systemLoader); /* * 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定 如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为 * 系统类加载器的加载路径 */ Enumeration<URL> em1 = systemLoader.getResources(""); while (em1.hasMoreElements()) { //系统类加载器的加载路径:程序运行的当前路径 System.out.println(em1.nextElement()); } // 获取系统类加载器的父类加载器:得到扩展类加载器 ClassLoader extensionLader = systemLoader.getParent(); //扩展类的加载器路径:java_homeinetc System.out.println("扩展类加载器:" + extensionLader); System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); //这里会输出null,因为根类加载器并没有继承ClassLoader这个抽象类,所以返回是null。它是用C++写的,无法直接获取 System.out.println("扩展类加载器的parent:" + extensionLader.getParent()); } }
- 创建并使用自定义的类加载器
import java.io.*; import java.lang.reflect.*; public class CompileClassLoader extends ClassLoader { // 读取一个文件的内容 private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int)len]; try( FileInputStream fin = new FileInputStream(file)) { // 一次读取class文件的全部二进制数据 int r = fin.read(raw); if(r != len) throw new IOException("无法读取全部文件:" + r + " != " + len); return raw; } } // 定义编译指定Java文件的方法 private boolean compile(String javaFile) throws IOException { System.out.println("CompileClassLoader:正在编译 " + javaFile + "..."); // 调用系统的javac命令 Process p = Runtime.getRuntime().exec("javac " + javaFile); try { // 其他线程都等待这个线程完成 p.waitFor(); } catch(InterruptedException ie) { System.out.println(ie); } // 获取javac线程的退出值 int ret = p.exitValue(); // 返回编译是否成功 return ret == 0; } // 重写ClassLoader的findClass方法 protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; // 将包路径中的点(.)替换成斜线(/)。 String fileStub = name.replace("." , "/"); String javaFilename = fileStub + ".java"; String classFilename = fileStub + ".class"; File javaFile = new File(javaFilename); File classFile = new File(classFilename); // 当指定Java源文件存在,且class文件不存在、或者Java源文件 // 的修改时间比class文件修改时间更晚,重新编译 if(javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // 如果编译失败,或者该Class文件不存在 if(!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException( "ClassNotFoundExcetpion:" + javaFilename); } } catch (IOException ex) { ex.printStackTrace(); } } // 如果class文件存在,系统负责将该文件转换成Class对象 if (classFile.exists()) { try { // 将class文件的二进制数据读入数组 byte[] raw = getBytes(classFilename); // 调用ClassLoader的defineClass方法将二进制数据转换成Class对象 clazz = defineClass(name,raw,0,raw.length); } catch(IOException ie) { ie.printStackTrace(); } } // 如果clazz为null,表明加载失败,则抛出异常 if(clazz == null) { throw new ClassNotFoundException(name); } return clazz; } // 定义一个主方法 public static void main(String[] args) throws Exception { // 如果运行该程序时没有参数,即没有目标类 if (args.length < 1) { System.out.println("缺少目标类,请按如下格式运行Java源文件:"); System.out.println("java CompileClassLoader ClassName"); } // 第一个参数是需要运行的类 String progClass = args[0]; // 剩下的参数将作为运行目标类时的参数, // 将这些参数复制到一个新数组中 String[] progArgs = new String[args.length-1]; System.arraycopy(args , 1 , progArgs , 0 , progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); // 加载需要运行的类 Class<?> clazz = ccl.loadClass(progClass); // 获取需要运行的类的主方法 Method main = clazz.getMethod("main" , (new String[0]).getClass()); Object[] argsArray = {progArgs}; main.invoke(null,argsArray); } }
- 用类加载器的方式管理资源和配置文件
1,读取输入流:配置文件要放在java项目的内部,读取的时候直接写路径就可以了。这个是系统默认的,他会自己计算出位置的,所以写的时候直接写就可以了。比如:Inputstream ips = new FileInputStream("Linkin.properties")。JDK的原话是这样子说的,注意理解:通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。创建一个新 FileDescriptor 对象来表示此文件连接。
2,读取资源:ClassLoader的getResourceAsStream()方法,或者Class的getResourceAsStream()方法,里面的参数是从classpath类路径下去寻找的,可以写绝对路径,也可以写相对路径,在框架中使用很多。注意理解:这里只是在查找一个普通的资源文件,不想上面那样子给对应的系统建立一个连接,而是从classPath路径里面去找。所以这里要不写相对路径(直接写),要不写绝对路径(/开头)