类的加载classload和类对象的生成
在Java中最重要的可以说就是类的加载了。不论我们编写的功能多么复杂,或是多么简单,永远逃离不开的,就是将这个类从class文件加载到JVM中来。
类的加载过程
首先我们要了解一下类的加载过程,包括:加载、连接(验证、准备、解析)、初始化、使用、卸载。
加载:将根据类的全限定名找到对应的Class文件,将它加载进JVM中,并生成Class对象保存在堆中。
连接:
验证:检查加载进来的类信息是否满足我们JVM的规范。
准备:对类中的静态变量分配内存空间,并赋予原始值。对常量直接赋予指定的值。
解析:将类中的符号引用转变为直接引用。
初始化:为类中的静态变量赋值,执行静态代码块。
下面我们用一个类来验证一下:
public class Main7 {
private final int z = ;
private final static int k = ;
private static int i = ;
private int j = ;
static {
i = ;
}
{
i = ;
j=;
}
public static void main(String[] args) {
}
}
如上,我们定义一个Main7类,并对类中的每一步都打上断点:

然后点击debug运行:
第一步:程序最先进入到第9行代码,此时查看我们最下面的静态成员中,k由于是final static,被直接赋予了我们给它指定的值1。而i由于只是一个static,它被先赋予了默认值0。至于我们其他的两个变量z和j,此时是没有被初始化的。
注意:第八行代码在我们的程序运行中并没有被debug进入断点,但是实际上它是最先和i一起被初始化的。即说明,加载和连接两个步骤,是无法被我们的debug进入的。我们这里能进入断点的,也仅仅只是初始化步骤。(第九行之所以能进入是因为我们对i赋予了i=5,如果我们只定义static int i,则这行也不会被进入debug)

第二步:执行静态代码块。

第三步:结束。
由于我们的main方法中并没有内容,因此我们不会创建任何自定义类的对象。Main7中的static变量与static代码块之所以会被初始化,也是因为这是作为main方法所在的类,会被加载进JVM。
总结:由上面的结果可以总结如下几点:
1.类只会在第一次被调用时候进行加载。这个调用包括Main方法所在的类、调用类中的静态成员变量、执行类的静态方法、通过反射创建对象、new一个对象、子类被初始化。
2.类的加载不会初始化非static变量,也不会执行非static代码块。
类加载器
JDK默认给我们提供了三个类加载器:
BootStrap ClassLoad:最顶级的类加载器,使用C++编写,由JVM启动,默认加载%JAVA_HOME%/lib下的jar包和类。
Extension ClassLoad:扩展加载器,由BootStrap ClassLoad启动,父类加载器是BootStrap ClassLoad,默认加载%JAVA_HOME%/lib/ext下的jar包和类。
Application ClassLoad:应用加载器,由BootStrap ClassLoad启动,父类加载器是Extension ClassLoad。默认加载classpath下的jar包和类。
关系图如下:

我们查看一个自定义类的加载类:
public static void main(String[] args) {
ClassLoader cl = Main7.class.getClassLoader();
while (cl != null) {
System.out.println(cl.toString());
cl = cl.getParent();
}
}
打印结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@77459877
可以看到,上面只打印出来了两个ClassLoader对象,一个是AppClassLoader,这是自定义类的加载器,另一个是ExtClassLoader,这是自定义类加载器的父类加载器。
而我们再次通过getParent()方法获取ExtClassLoader对象的父类加载器时候,返回的结果等于null,因此跳出了循环。
我们再尝试一个由BootStrap ClassLoader加载的类String,查看它的类加载类
public static void main(String[] args) {
ClassLoader cl = String.class.getClassLoader();
System.out.println(cl == null ? "cl is null" : cl.toString());
}
打印结果:
cl is null
虽然BootStrap ClassLoader是ExtClassLoader的父类加载器,但是由于它是C++编写,因此在Java代码中,并没有任何的体现,如果一个类的类加载器是null,那么它就是由BootStrap ClassLoader启动。
下面我们来检验一下上面的说法,首先利用类加载器的双亲委派机制来确认。
双亲委派机制:类加载器加载一个类时,会先交由它的父类加载器加载,如果父类加载器的加载范围中有全限定名相同的类文件,则由父类加载器加载这个类,子类加载器不再加载。
意即:自定义加载器加载一个类Aclass,首先交给父类加载器AppClassLoader,AppClassLoader再交由它的父类加载器ExClassLoader,ExtClassLoader再交由它的父类加载器BootStrapClassLoader,BootStrapClassLoader没有父类加载器,因此检查自己的加载范围%JAVA_HOME%/lib下有没有这个类,没有则再由ExtClassLoader检查它的加载范围%JAVA_HOME%/lib/ext下有没有这个类,没有则再有AppClassLoader检查classpath下有没有这个类,没有则再交由自定义类加载器去它的路径下加载。其中一旦有一个类加载器找到这个类的class文件,就会由这个类加载器进行加载,它的子类加载器而不会在进行处理。
下面我们来查看一下类加载器(ClassLoader)的加载方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
它这里接收一个全限定的二进制类名,然后调用loadClass(name,false)方法
// 这个protected可以看到,是允许我们通过自定义类加载器来重写这个方法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) { //这里加锁,每一次只会有一个类被加载进来。而不会同时加载多个类。
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //首先检查Class对象中是否已经包含了这个类。即这个类是否已经被加载过了。
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //首先判断父类加载器不等于null,也就是说父类加载器不是BootStrap ClassLoader
c = parent.loadClass(name, false); //交由父类加载器加载。双亲委派机制
} else {
c = findBootstrapClassOrNull(name); //否则的话,从BootStrapClassLoader中查找该类
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) { //父类加载器中没有对应的class文件
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); //调用findClass(name)从当前的类加载器中加载该类 // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从上面的代码里面可以看到,ClassLoader中明确指定了,当parent == null时,会调用findBootstrapClassOrNull(String)方法从BootStrapClassLoader中加载相关的类。
而且从ClassLoader的源码中我们也可以看到,如果我们要自定义自己的类加载器,只要继承ClassLoader,并重写loadclass(String)或findClass(String)方法即可。
下面我们来查看一下ClassLoader自带的findClass(String)方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可见,ClassLoader在这里采用了模版方法模式,将详细的加载类文件的方法交由它的子类去实现。
我们定义一个自定义类加载器
public class MyClassLoader extends ClassLoader {
// 加载器名称
private String name;
// 加载器的加载路径
private String path;
public MyClassLoader(String name, String path) {
super(); // 采用默认的父类加载器,即为调用它的类的类加载器。一般是appClassLoader
this.name = name;
this.path = path;
}
public MyClassLoader(ClassLoader classLoader, String name, String path) {
super(classLoader); //指定父类加载器
this.name = name;
this.path = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = readClassFile(name); //根据名称找到文件,并转为字节数组
return super.defineClass(name, bytes, , bytes.length); //将字节数组转为Class对象。
}
private byte[] readClassFile(String name) {
byte[] bytes = null;
InputStream is = null;
String filename = path + "/" + name.replaceAll("\\.", "/") + ".class";
File file = new File(filename);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = ;
while ((tmp = is.read()) != -) {
os.write(tmp);
}
bytes = os.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bytes;
}
}
我们定义一个用来被加载的测试类,将它编译后的class文件放在D://tmp目录下
public class Main5 {
Main5() {
System.out.println("Main5:" + this.getClass().getClassLoader().toString());
}
}

客户端调用代码
public class Main11 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader mcl = new MyClassLoader("yxf", "d://tmp");
Class c = mcl.loadClass("Main5");
c.newInstance();
}
}
输出结果:
Main5:main11.MyClassLoader@5b2133b1
可见打印出来的类加载器正是我们自定义的MyClassLoader。
假如我们在classpath下同样也存放一个Main5的class对象。
再次运行我们的客户端调用代码,输出结果:
Main5:sun.misc.Launcher$AppClassLoader@18b4aac2
这一次由于双亲委派机制,我们Main5类被加载时使用的是AppClassLoader。
我们修改一下客户端代码:
public class Main11 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader mcl = new MyClassLoader(null, "yxf", "d://tmp"); //指定了父类加载器null,即BootStrapClassLoader
Class c = mcl.loadClass("Main5");
c.newInstance();
}
}
再次运行,输出结果:
Main55:main11.MyClassLoader@5b2133b1
这一次是由于我们给自定义的类加载器指定了它的父类加载器BootStrapClassLoader,因此,即使我们在classpath下存放了一个Main5.class,也不会调用到AppClassLoader中去。
类的加载classload和类对象的生成的更多相关文章
- Java类的加载
1.类的加载步骤 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载.连接.初始化三步来实现对这个类的初始化 加载:将class文件读入内存,并为之创建一个Class对象,任何类被使用 ...
- Java虚拟机JVM学习02 类的加载概述
Java虚拟机JVM学习02 类的加载概述 类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对 ...
- java 27 - 1 反射之 类的加载器
说到反射,首先说类的加载器. 类的加载: 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化. 加载: 就是指将class文件读入内存,并为之 ...
- Java类的加载、链接和初始化
一.Java的类加载机制回顾与总结: 我们知道一个Java类要想运行,必须由jvm将其装载到内存中才能运行,装载的目的就是把Java字节代码转换成JVM中的java.lang.Class类的对象.这样 ...
- jvm系列 (五) ---类的加载机制
类的加载机制 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 jvm系列 (四) ---强.软.弱.虚引用 我的博客目录 什么是类 ...
- 24.类的加载机制和反射.md
目录 1类的加载连接和初始化 1.1类的加载过程 1.2类的加载器 1.2.1类的加载机制 1类的加载连接和初始化 1.1类的加载过程 类的加载过程简单为分为三步:加载->连接->初始化 ...
- JAVA类的加载、连接与初始化
JAVA类的加载.连接与初始化 类的声明周期总共分为5个步骤1.加载2.连接3.初始化4.使用5.卸载 当java程序需要某个类的时候,java虚拟机会确保这个类已经被加载.连接和初始化,而连接这个类 ...
- java 类的加载、连接和初始化
JVM和类 调用Java命令运行Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序启动了多少条线程,创建了多少个变量,它们都处于该Java虚拟机进程里,共享该JVM进程的内存区. ...
- JVM虚拟机系列(一)类的加载
JAVA虚拟机系列(一) 类的加载 目录 1 类的初始化过程 2 详解初始化时的各个阶段 一.类初始化的过程 先来看一个CLASS文件在整体生命周期里会遇到的阶段: xxxx.class ---> ...
随机推荐
- thinkphp 模板主题
一个模块如果需要支持多套模板文件的话,就可以使用模板主题功能. 默认情况下,没有开启模板主题功能,如果需要开启,设置 DEFAULT_THEME 参数即可: 大理石平台精度等级 // 设置默认的模板主 ...
- JavaSE_11_File类、递归
1.1 概述File类 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. 1.2 构造方法 public File(String pathname ...
- CSS或HTML如何实现文字下面加点?
就像word里文字加着重号一样,在字的下面加一个点,用CSS怎么做?注意,我说的是下面加点,不是文字加粗或倾斜,请不要回答<strong>或<em>之类的. 把要着重加点的文字 ...
- PAT甲级——A1005 Spell It Right
题目描述 Given a non-negative integer N, your task is to compute the sum of all the digits of N, and out ...
- adb命令总结
- mac下解压bin文件
在mac下要解压Android-ndk-r10e-darwin-x86_64.bin文件. 1.进入文件所在目录,修改文件的读取权限 chmod a+x android-ndk-r10e-darwin ...
- shell下时间日期的加减乘除运算
首先我们先来说说什么是shell下的时间戳: 自1970年1月1日(00:00:00 UTC/GMT)以来的秒数.它也被称为Unix时间戳(Unix Timestam.Unix epoch.POSIX ...
- 最大似然估计(Maximum likelihood estimation)
最大似然估计提供了一种给定观察数据来评估模型参数的方法,即:"模型已定,参数未知".简单而言,假设我们要统计全国人口的身高,首先假设这个身高服从服从正态分布,但是该分布的均值与方差 ...
- CodeChef TRIPS-Children Trips 树上分块
参考文献国家集训队2015论文<浅谈分块在一类在线问题的应用>-邹逍遥 题目链接 题目大意 一棵n个节点的树,树的每条边长度为1或2,每次询问x,y,z. 要求输出从x开始走,每次只能走到 ...
- Leetcode962. Maximum Width最大宽度坡 Ramp
给定一个整数数组 A,坡是元组 (i, j),其中 i < j 且 A[i] <= A[j].这样的坡的宽度为 j - i. 找出 A 中的坡的最大宽度,如果不存在,返回 0 . 示例 ...