Java的类加载
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是Java虚拟机的类加载机制
----类加载的大致过程
类的加载的过程一共分为三个步骤:加载、链接和初始化
加载:是类加载的一个阶段,由类加载器执行,查找字节码,并创建一个Class对象(只是创建)。
这里就引出了另外一个概念,java的动态加载,即java程序在运行时并不一定被完整加载,只有当发现该类还没有加载时,才去本地或远程查找类的.class文件并验证和加载
并且当程序创建了第一个对类的静态成员的引用(如类的静态变量、静态方法、构造方法——构造方法也是静态的)时,才会加载该类
所以加载并不意味着这个字节码文件就要被初始化
链接:链接其实分为三个部分,验证、准备和解析。
验证:即验证二进制字节码文件在结构上的正确性,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
准备:准备阶段主要是创建静态域,分配空间,给这些域设默认值,为类变量分配内存并且设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配
注意这里不会为实例变量分配空间,实例变量将会在对象实例化的时候一起进入java堆中。假如:
public static int value = 13;
那变量value在准备阶段的值就不是13而是0,因为这时候尚未开始执任何的java方法
解析:解析的过程就是对类中的接口、类、方法、变量的符号引用进行解析并定位,解析成直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址),并保证这些类被正确的找到
初始化:类加载的最后一步,真正开始执行类中定义的java程序代码
static{}是在第一次初始化时执行,且只执行一次
类的加载方式可以分为:类的主动引用和类的被动引用
类的主动引用(一定会发生类的初始化) --new一个类的对象 --调用类的静态成员(除了final常量)和静态方法 --使用java.lang.reflect包的方法对类进行反射调用(Class.forName(xxx.xxx.xxx)) --当初始化一个类,如果其父类没有被初始化,则先初始化他的父类 --当要执行某个程序时,一定先启动main方法所在的类 类的被动引用(不会发生类的初始化) --当访问一个静态变量时,只有真正声明这个静态变量的类才会被初始化(通过子类引用父类的静态变量,不会导致子类初始化) --通过数组定义类应用,不会触发此类的初始化 A[] a = new A[10]; --引用常量(final类型)不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
----例子1结合分析:
class Singleton{
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0; private Singleton(){
value1++;
value2++;
} public static Singleton getInstance(){
return singleton;
}
} class Singleton2{
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2(); private Singleton2(){
value1++;
value2++;
} public static Singleton2 getInstance2(){
return singleton2;
} } public class Test7 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1:" + singleton.value1);
System.out.println("Singleton1 value2:" + singleton.value2); Singleton2 singleton2 = Singleton2.getInstance2();
System.out.println("Singleton2 value1:" + singleton2.value1);
System.out.println("Singleton2 value2:" + singleton2.value2);
}
}
输出的结果:
Singleton1 value1:1
Singleton1 value2:0
Singleton2 value1:1
Singleton2 value2:1
过程:
1 首先执行main中的Singleton singleton = Singleton.getInstance();
2 类的加载:加载类Singleton
3 类的验证
4 类的准备:为静态变量分配内存,设置默认值。这里为singleton(引用类型)设置为null,value1,value2(基本数据类型)设置默认值0
5 类的初始化(按照赋值语句进行修改):
执行private static Singleton singleton = new Singleton();
执行Singleton的构造器:value1++;value2++; 此时value1,value2均等于1
执行
public static int value1;
public static int value2 = 0;
此时value1=1,value2=0 6.Singleton2和Singleton的不同之处在于执行private static Singleton singleton = new Singleton(); 这句的时候在变量赋值之后,
所以,当虚拟机已经为value1和value2分配好内存后设置默认值之后(准备过程),又执行了
public static int value1;
public static int value2 = 0;
此时value1,value2都是0;
接着因为实例化对象,调用了构造器方法,value1和value2都变成了1 两者的区别在于:调用构造器的顺序不同
----例子2结合分析
class B {
static int value = 100;
static {
System.out.println("Class B is initialized");
}
} class A extends B {
static {
System.out.println("Class A is initialized");
}
} public class Test4 {
public static void main(String[] args) {
System.out.println(A.value);
}
}
输出结果:
Class B is initialized
100
过程:
当访问一个静态变量时,只有真正声明这个静态变量的类才会被初始化(通过子类引用父类的静态变量,不会导致子类初始化),
虽然这里的A来引用了value,但是value是在父类中静态声明的,所以子类A并不会初始化
----例子3结合分析
class StaticBlock {
static final int c = 3; static final int d; static int e = 5;
static {
d = 5;
e = 10;
System.out.println("Initializing");
} StaticBlock() {
System.out.println("Building");
}
} public class Test4 {
public static void main(String[] args) {
System.out.println(StaticBlock.c);
System.out.println(StaticBlock.d);
System.out.println(StaticBlock.e);
}
}
输出结果:
3
Initializing
5
10
过程:
这里将要引出另外一个概念:编译时常量,即被static、final所修饰的常量,这种常量不需要初始化就可以被加载,就不会引起类的初始化。
所以,上边的程序中c就是一个编译时常量,不会引起类的初始化,直接加载输出,而d是个静态的变量,会引起类的加载,所以会先加载static块的内容,然后d就被赋值成5。
变量e呢,它有点类似上边例子1的情况,在准备阶段设置默认值为0后,然后又被赋值为5,接着类的加载需要执行static块的内容,所以e又被赋值成10
----类加载器
在java中,类的加载,离不开类加载器的配合,一般分为三个部分:启动类加载器、扩展类加载器和应用程序类加载器。当然,有时候我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,是一种任务委派模式。
三大类加载器
1)启动类加载器,使用c++编写(BootStrap),负责加载rt.jar,没有父类
2)扩展类加载器,java实现(ExtClassLoader),负责加载<JAVA_HOME>/lib/ext目录下的,父类加载器为null
3)应用程序加载器,java实现(AppClassLoader) ,也成为系统类加载器,负责加载ClassPath上所指定的类库,
如果应用程序没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器,父类加载器为ExtClassLoader
双亲委派工作原理:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。
双亲委派的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,如果不使用这种委托方式,那么可以随时使用自定义的String来动态替Java核心的API中定义的类型,这样会存在非常大的安全隐患,而父类委托方式可以避免这种情况,因为String在启动的时候就已经被加载,所以,用户自定义类无法加载一个自定义的ClassLoader
一些重要方法:
----loaderClass方法
loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现。源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先从缓存查找该class对象,找到就不用重新加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果找不到,则委托给父类加载器去加载
c = parent.loadClass(name, false);
} else {
//如果没有父类,则委托给启动加载器去加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) {
// If still not found, then invoke findClass in order
// 如果都没有找到,则通过自定义实现的findClass去查找并加载
c = 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;
}
}
----findClass(String)方法
目的是从本地文件系统使用实现的类装载器装载一个类。为了创建自己的类装载器,应该扩展ClassLoader类,这是个抽象类,可创建一个FileClassLoaderextends ClassLoader,然后覆盖ClassLoader类findClass(String name)方法,这个方法通过类的名字来得到一个对象
public Class findClass(String name){
byte[] data = loadClassData(name);
return defineClass(name,data,0,data.length);
}
----defineClass(byte[] b, int off, int len)方法
defineClass方法接受由原始字节组成的数组,并且把它转成Class对象。原始数组包含如从文件系统或者网络装入的数据,defineClass管理着JVM的许多复杂的实现层面——它把字节码分析成运行时的数据结构、检验有效性等,因为defineClass方法被标记为final,所以也不能覆盖它
----resolveClass(Class≺?≻ c)
使用该方法可以使用类的Class对象创建完成也同时被解析
?----怎么确定两个类是否相等
比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类必定不相等
?----什么是热部署类加载器
所谓的热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象,于JVM在加载类之前会检测请求的类是否已加载过(即在loadClass()方法中调用findLoadedClass()方法),如果被加载过,则直接从缓存获取,不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次,多次加载将报错,因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,以实现所谓的热部署。
?----破坏双亲委派模型-线程上下文加载器
简单来说,java应用中的很多服务都要依靠第三方的支持,如jdbc之类的,那么它们为java提供的接口是放在rt.jar包下,由BootStrap类加载器加载,然而它们这些接口的实现;类和方法却放在ClassPath下,这就很难受了,因为java程序当要使用第三方的接口服务时,那么这些接口是有启动类加载器加载,可是没办法加载具体的接口实现类,由于双亲委派模型存在,BootStrap类加载器又没办法反向去找AppClassLoader类加载器进行实现。
为了解决这个问题,便引入了线程上下文加载器,它默认是AppClassLoader类加载器,它将jdbc等SPI接口实现类进行加载,就是我们常见的ClassPath路径下的那些jar包之类的,然后BootStrap类加载器就可以去委托该加载器,将相关的实现类加载到内存中以便使用
本人菜鸟一枚,拜读了诸多大佬文章之后,以及查阅书籍,做了简单的小结记录,以便今后面试查阅使用
主要参考资料:https://blog.csdn.net/javazejian/article/details/73413292
https://www.cnblogs.com/zhguang/p/3154584.html#classLoader
《深入了解JAVA虚拟机》
Java的类加载的更多相关文章
- java自定义类加载器
前言 java反射,最常用的Class.forName()方法.做毕设的时候,接收到代码字符串,通过 JavaCompiler将代码字符串生成A.class文件(存放在classpath下,也就是ec ...
- Java虚拟机类加载机制——案例分析
转载: Java虚拟机类加载机制--案例分析 在<Java虚拟机类加载机制>一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的最后留了一个悬念给各位,这里来揭开这个悬 ...
- JAVA 初识类加载机制 第13节
JAVA 初识类加载机制 第13节 从这章开始,我们就进入虚拟机类加载机制的学习了.那么什么是类加载呢?当我们写完一个Java类的时候,并不是直接就可以运行的,它还要编译成.class文件,再由虚拟机 ...
- Java 的类加载机制
Java 的类加载机制 来源 https://www.cnblogs.com/xiaoxi/p/6959615.html 一.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内 ...
- Java基础-类加载机制与自定义类Java类加载器(ClassLoader)
Java基础-类加载机制与自定义类Java类加载器(ClassLoader) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 关于类加载器的概念和分类我就不再废话了,因为我在之前的笔 ...
- Java 的类加载顺序
Java 的类加载顺序 一.加载顺序:先父类后子类,先静态后普通 1.父类的静态成员变量初始化 2.父类的静态代码块 3.子类的静态成员变量初始化 4.子类的静态代码块 5.父类的普通成员变量初始化 ...
- 深入理解Java虚拟机---类加载机制(简略版)
类加载机制 谈起类加载机制,在这里说个题外话,当初本人在学了两三个月的Java后,只了解了一些皮毛知识,就屁颠屁颠得去附近学校的招聘会去蹭蹭面试经验,和HR聊了一会后开始了技术面试,前抛出了两个简单的 ...
- ClassLoader Java中类加载出现在哪个阶段,编译期和运行期? 类加载和类装载是一样的吗
1.ClassLoader Java中类加载出现在哪个阶段,编译期和运行期? 类加载和类装载是一样的吗? :当然是运行期间啊,我自己有个理解误区,改正后如下:编译期间编译器是不去加载类的,只负责编译而 ...
- 我竟然不再抗拒 Java 的类加载机制了
很长一段时间里,我对 Java 的类加载机制都非常的抗拒,因为我觉得太难理解了.但为了成为一名优秀的 Java 工程师,我决定硬着头皮研究一下. 01.字节码 在聊 Java 类加载机制之前,需要先了 ...
- 面试官,不要再问我“Java虚拟机类加载机制”了
关于Java虚拟机类加载机制往往有两方面的面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程.其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解. 面试题试水 现在有这样一道判断程 ...
随机推荐
- Ceph之对象存储网关RADOS Gateway(RGW)
一.Ceph整体架构及RGW在Ceph中的位置 1.Ceph的整体架构 Ceph是一个统一的.分布式的的存储系统,具有优秀的性能.可靠性和可扩展性.Ceph支持对象存储(RADOSGW).块存储(RB ...
- HDU1253:胜利大逃亡
传送门 题意 逃离迷宫 分析 用优先队列和队列都可以,但是我vis数组写在取队列首节点就MLE了,放在放入节点的地方就ac了,看来是一种固定写法,在放入节点的地方判断,可以防止放入无效点到队列,防止队 ...
- ACboy needs your help HDU - 1712
ACboy needs your help HDU - 1712 ans[i][j]表示前i门课共花j时间最大收益.对于第i门课,可以花k(0<=k<=j)时间,那么之前i-1门课共花j- ...
- 题解报告:hdu 2602 Bone Collector(01背包)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2602 Problem Description Many years ago , in Teddy’s ...
- RHEL5.6配置本地yum源
试验环境:RedHat Enterprise Linux 5.6(虚拟机) 一.挂载镜像 配置yum源第一步需要挂载镜像,或者直接复制操作系统的光盘文件至操作系统目录中. 挂载镜像命令如下: moun ...
- NDK(10)Android.mk各属性简介,Android.mk 常用模板--未完
参考 : http://blog.csdn.net/hudashi/article/details/7059006 1. Android.mk简介 Android.mk文件是GNU Makefile的 ...
- Android偏好设置(6)应用和监听各偏好参数
Reading Preferences By default, all your app's preferences are saved to a file that's accessible fro ...
- C# 操作Access的Ole对象[转]
原文链接 OLE对象数据类型 (1)OLE 对象用于使用 OLE 协议在其他程序中创建的 OLE 对象,如 Microsoft Word 文档. Microsoft Excel 电子表格.图片.声音或 ...
- Eclipse快捷键集合
***eclipse查看哪个方法被调用:选中,右键选择Open call Hierarchy 选择要查看的方法: ctrl + alt + h 查看一个类被那些类继承或者实现: F ...
- 准确计算CoreText高度的方法:
- (int)getAttributedStringHeightWithString:(NSAttributedString *) string WidthValue:(int) width { ; ...