当程序使用某个类时,如果该类还没被初始化,加载到内存中,则系统会通过加载、连接、初始化三个过程来对该类进行初始化。该过程就被称为类的初始化
指将类的class文件读入内存,并为之创建一个java.lang.Class的对象
类加载器通常无须等到“首次使用”该类时才加载该类,JVM允许系统预先加载某些类
类加载器就是负责加载所有的类,将其载入内存中,生成一个java.lang.Class实例。一旦一个类被加载到JVM中之后,就不会再次载入了。
类加载器之间的父子关系并不是继承关系,是类加载器实例之间的关系
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
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初始化步骤