当程序使用某个类时,若是该类还没被初始化,加载到内存中,则系统会经过加载、链接、初始化三个过程来对该类进行初始化。该过程就被称为类的初始化java
指将类的class文件读入内存,并为之建立一个java.lang.Class的对象mysql
从本地文件系统加载的class文件git
从JAR包加载class文件github
从网络加载class文件sql
把一个Java源文件动态编译,并执行加载数组
类加载器一般无须等到“首次使用”该类时才加载该类,JVM容许系统预先加载某些类缓存
类加载器就是负责加载全部的类,将其载入内存中,生成一个java.lang.Class实例。一旦一个类被加载到JVM中以后,就不会再次载入了。微信
根类加载器(Bootstrap ClassLoader):其负责加载Java的核心类,好比String、System这些类网络
拓展类加载器(Extension ClassLoader):其负责加载JRE的拓展类库网站
系统类加载器(System ClassLoader):其负责加载CLASSPATH环境变量所指定的JAR包和类路径
用户类加载器:用户自定义的加载器,以类加载器为父类
类加载器之间的父子关系并非继承关系,是类加载器实例之间的关系
public static void main(String[] args) throws IOException { ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载"); Enumeration<URL> em1 = systemLoader.getResources(""); while (em1.hasMoreElements()) { System.out.println(em1.nextElement()); } ClassLoader extensionLader = systemLoader.getParent(); System.out.println("拓展类加载器" + extensionLader); System.out.println("拓展类加载器的父" + extensionLader.getParent()); }
结果
系统类加载 file:/E:/gaode/em/bin/ 拓展类加载器sun.misc.Launcher$ExtClassLoader@6d06d69c 拓展类加载器的父null
为何根类加载器为NULL?
根类加载器并非Java实现的,并且因为程序一般须访问根加载器,所以访问扩展类加载器的父类加载器时返回NULL
全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其余Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入
父类委托,先让父类加载器试图加载该类,只有在父类加载器没法加载该类时才尝试从本身的类路径中加载该类
缓存机制,缓存机制将会保证全部加载过的Class都会被缓存,当程序中须要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为何修改了Class后,必须重启JVM,程序的修改才会生效
URLClassLoader为ClassLoader的一个实现类,该类也是系统类加载器和拓展类加载器的父类(继承关系)。它既能够从本地文件系统获取二进制文件来加载类,也能够远程主机获取二进制文件来加载类。
两个构造器
URLClassLoader(URL[] urls):使用默认的父类加载器建立一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器建立一个ClassLoader对象,其余功能与前一个构造器相同
import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import com.mysql.jdbc.Driver; public class GetMysql { private static Connection conn; public static Connection getConn(String url,String user,String pass) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{ if(conn==null){ URL[]urls={new URL("file:mysql-connector-java-5.1.18.jar")}; URLClassLoader myClassLoader=new URLClassLoader(urls); Driver driver=(Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); Properties pros=new Properties(); pros.setProperty("user", user); pros.setProperty("password", pass); conn=driver.connect(url, pros); } return conn; } public static method1 getConn() throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{ URL[]urls={new URL("file:com.em")}; URLClassLoader myClassLoader=new URLClassLoader(urls); method1 driver=(method1) myClassLoader.loadClass("com.em.method1").newInstance(); return driver; } public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { System.out.println(getConn("jdbc:mysql://10.10.16.11:3306/auto?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true", "jiji", "jiji")); System.out.println(getConn()); } }
得到URLClassLoader对象后,调用loanClass()方法来加载指定的类
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { // 读取一个文件的内容 @SuppressWarnings("resource") private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int) len]; FileInputStream fin = new FileInputStream(file); // 一次读取class文件的所有二进制数据 int r = fin.read(raw); if (r != len) throw new IOException("没法读取所有文件" + r + "!=" + len); fin.close(); 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("ClassNotFoundException:" + 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(); } } // 若是claszz为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 cl = new CompileClassLoader(); // 加载须要运行的类 Class<?> clazz = cl.loadClass(progClass); // 获取须要运行的类的主方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = { progargs }; main.invoke(null, argsArray); } }
JVM中除了根类加载器以外的全部类的加载器都是ClassLoader子类的实例,经过重写ClassLoader中的方法,实现自定义的类加载器
loadClass(String name,boolean resolve):为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取制定累对应的Class对象
findClass(String name):根据指定名称来查找类
推荐使用findClass方法
当类被加载后,系统会为之生成一个Class对象,接着将会进入链接阶段,连接阶段负责把类的二进制数据合并到JRE中
三个阶段
验证:检验被加载的类是否有正确的内部结构,并和其余类协调一致
准备:负责为类的类变量分配内存。并设置默认初始值
解析:将类的二进制数据中的符号引用替换成直接引用
JVM负责对类进行初始化,主要对类变量进行初始化
在Java中对类变量进行初始值设定有两种方式:①声明类变量是指定初始值②使用静态代码块为类变量指定初始值
JVM初始化步骤
假如这个类尚未被加载和链接,则程序先加载并链接该类
假如该类的直接父类尚未被初始化,则先初始化其直接父类
假如类中有初始化语句,则系统依次执行这些初始化语句
建立类实例。也就是new的方式
调用某个类的类方法
访问某个类或接口的类变量,或为该类变量赋值
使用反射方式强制建立某个类或接口对应的java.lang.Class对象
初始化某个类的子类,则其父类也会被初始化
直接使用java.exe命令来运行某个主类
更多内容能够关注微信公众号,或者访问AppZone网站