搞懂JVM类加载机制
有这样一道面试题:
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 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
稍后会带来分析。
一 类加载机制
JVM类加载分为5个过程:加载,验证,准备,解析,初始化,使用,卸载,如下图所示:
下面来看看加载,验证,准备,解析,初始化这5个过程的具体动作。
1.1 加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。
在加载阶段,JVM需要完成3件事:
1)通过类的全限定名获取该类的二进制字节流;
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
1.2 连接
1.2.1 验证
验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。
验证阶段会完成以下4个阶段的检验动作:
1)文件格式验证
2)元数据验证(是否符合Java语言规范)
3)字节码验证(确定程序语义合法,符合逻辑)
4)符号引用验证(确保下一步的解析能正常执行)
1.2.2 准备
准备是连接阶段的第二步,主要为静态变量在方法区分配内存,并设置默认初始值。
1.2.3 解析
解析是连接阶段的第三步,是虚拟机将常量池内的符号引用替换为直接引用的过程。
1.3 初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。
注:
1)当有父类且父类为初始化的时候,先去初始化父类;
2)再进行子类初始化语句。
什么时候需要对类进行初始化?
1)使用new该类实例化对象的时候;
2)读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
3)调用类静态方法的时候;
4)使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
5) 初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
6) 被标明为启动类的类(即包含main()方法的类)要初始化;
7)当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上情况称为对一个类进行主动引用,且有且只要以上几种情况需要对类进行初始化。
再回过头来分析一开始的面试题:
Singleton输出结果:1 0
原因:
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
Singleton2输出结果:1 1
原因:
1 首先执行main中的Singleton2 singleton2 = Singleton2.getInstance2();
2 类的加载:加载类Singleton2
3 类的验证
4 类的准备:为静态变量分配内存,设置默认值。这里为value1,value2(基本数据类型)设置默认值0,singleton2(引用类型)设置为null,
5 类的初始化(按照赋值语句进行修改):
执行
public static int value2 = 0;
此时value2=0(value1不变,依然是0);
执行
private static Singleton singleton = new Singleton();
执行Singleton2的构造器:value1++;value2++;
此时value1,value2均等于1,即为最后结果
二 类加载器
类加载器实现的功能是即为加载阶段获取二进制字节流的时候。
JVM提供了以下3种系统的类加载器:
启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。
类加载器之间的层次关系如下:
照片来源:http://www.importnew.com/25295.html
类加载器之间的这种层次关系叫做双亲委派模型。
双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。
双亲委派模型的工作过程
如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。
只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。
双亲委派模型的代码实现
双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。
1)首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
2)若父类加载器为空,则默认使用启动类加载器作为父加载器;
3)若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。
loadClass源代码如下:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
//1 首先检查类是否被加载
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//2 没有则调用父类加载器的loadClass()方法;
c = parent.loadClass(name, false);
} else {
//3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//4 若父类加载失败,抛出ClassNotFoundException 异常后
c = findClass(name);
}
}
if (resolve) {
//5 再调用自己的findClass() 方法。
resolveClass(c);
}
return c;
}
破坏双亲委派模型
双亲委派模型很好的解决了各个类加载器加载基础类的统一性问题。即越基础的类由越上层的加载器进行加载。
若加载的基础类中需要回调用户代码,而这时顶层的类加载器无法识别这些用户代码,怎么办呢?这时就需要破坏双亲委派模型了。
下面介绍两个例子来讲解破坏双亲委派模型的过程。
JNDI破坏双亲委派模型
JNDI是Java标准服务,它的代码由启动类加载器去加载。但是JNDI需要回调独立厂商实现的代码,而类加载器无法识别这些回调代码(SPI)。
为了解决这个问题,引入了一个线程上下文类加载器。 可通过Thread.setContextClassLoader()设置。
利用线程上下文类加载器去加载所需要的SPI代码,即父类加载器请求子类加载器去完成类加载的过程,而破坏了双亲委派模型。
Spring破坏双亲委派模型
Spring要对用户程序进行组织和管理,而用户程序一般放在WEB-INF目录下,由WebAppClassLoader类加载器加载,而Spring由Common类加载器或Shared类加载器加载。
那么Spring是如何访问WEB-INF下的用户程序呢?
使用线程上下文类加载器。 Spring加载类所用的classLoader都是通过Thread.currentThread().getContextClassLoader()获取的。当线程创建时会默认创建一个AppClassLoader类加载器(对应Tomcat中的WebAppclassLoader类加载器): setContextClassLoader(AppClassLoader)。
利用这个来加载用户程序。即任何一个线程都可通过getContextClassLoader()获取到WebAppclassLoader。
三 附上Tomcat类加载架构:
(图片来源:http://lib.csdn.net/article/java/60356)
Tomcat目录下有4组目录:
/common目录下:类库可以被Tomcat和Web应用程序共同使用;由 Common ClassLoader类加载器加载目录下的类库;
/server目录:类库只能被Tomcat可见;由 Catalina ClassLoader类加载器加载目录下的类库;
/shared目录:类库对所有Web应用程序可见,但对Tomcat不可见;由 Shared ClassLoader类加载器加载目录下的类库;
/WebApp/WEB-INF目录:仅仅对当前web应用程序可见。由 WebApp ClassLoader类加载器加载目录下的类库;
每一个JSP文件对应一个JSP类加载器。
---------------------
转自:https://blog.csdn.net/noaman_wgs/article/details/74489549
搞懂JVM类加载机制的更多相关文章
- 一夜搞懂 | JVM 类加载机制
前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创 ...
- 彻底搞懂JVM类加载器:基本概念
本文阅读时间大约9分钟. 写在前面 在Java面试中,在考察完项目经验.基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些J ...
- 一文教你读懂JVM类加载机制
Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...
- 一夜搞懂 | JVM 字节码执行引擎
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...
- 深入理解JVM虚拟机6:深入理解JVM类加载机制
深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...
- JVM基础系列第7讲:JVM 类加载机制
当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...
- JVM总结(四):JVM类加载机制
这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...
- JVM 类加载机制详解
如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lan ...
- Java虚拟机(四):JVM类加载机制
1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...
随机推荐
- spring-cloud-sleuth简单使用
快速开始 一.导入依赖 <!--链路追踪 start--> <dependency> <groupId>org.springframework.cloud</ ...
- Groovy中String转换Gstring用于动态插值
知识点是Groovy中的模板引擎 GStringTemplateEngine 第一个例子: def binding = [ firstname : "Grace", lastnam ...
- Spring_Spring与AOP_AspectJ基于XML的实现
一.前置通知 import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.as ...
- Jenkins-在节点上执行copy命令,将节点机上的文件拷贝到映射的网络驱动盘中报错,访问被拒绝 找不到指定驱动器
问题如标题,根据网友提供的解决方法,完美解决: 在jenkins中执行这个命令时报错 说无法访问. 重新映射一次可解决这个问题,添加一条 net use 命令
- libevent学习笔记 —— 牛刀小试:简易的服务器
回想起之前自己用纯c手动写epoll循环,libevent用起来还真是很快捷啊!重写了之前学习的时候的一个例子,分别用纯c与libevent来实现.嗯,为了方便对比一下,就一个文件写到黑了. 纯c版: ...
- html和css入门 (二)
CSS基础 什么是CSS 简单来说,层叠样式表(Cascading Style Sheet)是一种专门用来控制界面外观风格的文档. CSS发展历史 1996年 CSS 1.0 规范面世,其中加入了字体 ...
- sdoi 2017 r1游记
第一次参加省选... 不过幸亏我参加过WC和THUWC,还是有些经验的. 经验就是:多拿部分分(不过话说我的部分分大部分都丢了). D1: 第一题没有预处理斐波那契数列的幂,算复杂度算错了...丢了4 ...
- NOI 2016 区间 解题报告
题目描述 在数轴上有 n个闭区间 [l1,r1],[l2,r2],...,[ln,rn].现在要从中选出 m 个区间,使得这 m个区间共同包含至少一个位置.换句话说,就是使得存在一个 x,使得对于每一 ...
- 精选10款HTML5手机模板
1.Stroller | Mobile & Tablet Responsive Template 演示地址 购买地址 2.Ocean Mobile Template 演示地址 购买地址 ...
- Liunx一些命令
1.设置管理员的密码:sudo passwd root2.获取管理员权限su root3.查看IP地址sudo ifconfig -a4.创建一个文件sudo touch test.txt5.创建一个 ...