欢迎你们关注 github.com/hsfxuebao/j… ,但愿对你们有所帮助,要是以为能够的话麻烦给点一下Star哈html
转自:www.cnblogs.com/xjwhaha/p/1…java
jvm运行,有哪些重要的 组件,以下图git
共可分红三个大类github
下面挨个说明数据库
当咱们把 代码写完,编译成字节码 class文件后,打包运行,接下来就是jvm的工做了,jvm经过类加载器ClassLoader完成 类加载的过程,bootstrap
class file存在于本地硬盘上,能够理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来 , 根据这个文件实例化出n个如出一辙的实例。而这个模板就是咱们使用反射时常常看到的东西,Class对象,每一个类都对应一个此对象,存放类的元数据。api
示意图:数组
整个类加载过程 可分红三个部分,加载,连接(验证 --> 准备 --> 解析) ,初始化安全
加载class文件的方式可有如下几种markdown
连接分为三个子阶段:验证 --> 准备 --> 解析
验证(Verify):
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
例如:字节码文件(BinaryViewer查看),其开头均为 CAFE BABE ,若是出现不合法的字节码文件,那么将会验证不经过
准备(Prepare)
举例:
public class HelloApp {
private static int a = 1; //变量a在准备阶段会赋初始值,但不是1,而是0,在初始化阶段会被赋值为 1
public static void main(String[] args) {
System.out.println(a);
}
}
复制代码
解析(Resolve)
举例:
public class ClinitTest {
private int a = 1;
private static int c = 3;
public static void main(String[] args) {
int b = 2;
}
}
复制代码
类中有静态变量生成 clinit 方法
若没有static变量
public class ClinitTest {
private int a = 1;
public static void main(String[] args) {
int b = 2;
}
}
复制代码
则没有clinit方法
举例:
public class ClassInitTest {
private static int number = 10; //linking之prepare: number = 0 --> initial: 10 --> 20
static {
number = 20;
System.out.println(num);
}
public static void main(String[] args) {
System.out.println(ClassInitTest.num);//2
System.out.println(ClassInitTest.number);//10
}
}
复制代码
静态变量 number 的值变化过程以下
<clinit>()
不一样于类的构造器。(关联:构造器是虚拟机视角下的<init>()
,)<clinit>()
执行前,父类的<clinit>()
已经执行完毕<clinit>()
方法在多线程下被同步加锁(多个线程同时加载同一个类)JVM支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader )
从概念上来说,自定义类加载器通常指的是程序中由开发人员自定义的一类类加载器,可是Java虚拟机规范却没有这么定义,而是将全部派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
不管类加载器的类型如何划分,在程序中咱们最多见的类加载器始终只有3个,这里的四者之间是包含关系,不是上层和下层,也不是子父类的继承关系。 以下所示
代码:
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层:扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//对于用户自定义类来讲:默认使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
复制代码
经常使用加载器介绍:
启动类加载器(引导类加载器,Bootstrap ClassLoader)
扩展类加载器(Extension ClassLoader)
应用程序类加载器(系统类加载器,AppClassLoader)
代码:
public class ClassLoaderTest1 {
public static void main(String[] args) {
System.out.println("**********启动类加载器**************");
//获取BootstrapClassLoader可以加载的api的路径
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);//null
System.out.println("***********扩展类加载器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
//从上面的路径中随意选择一个类,来看看他的类加载器是什么:扩展类加载器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
复制代码
打印结果
String 类就在 其中的rt.jar中, 由 启动类加载器加载
扩展类加载器加载 lib下的 ext下的jar包类
**********启动类加载器**************
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_144/jre/classes
null
***********扩展类加载器*************
C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
sun.misc.Launcher$ExtClassLoader@7ea987ac
复制代码
注意,此为用户自定义,不是jvm规范中的 自定义 加载器,此处概述 ,后面详细介绍
为何须要自定义类加载器?
如何自定义类加载器?
ClassLoader类,它是一个抽象类,其后全部的类加载器都继承自ClassLoader(不包括启动类加载器)而且都定义在sun.misc.Launcher 类中,都是其子类,sun.misc.Launcher 它是一个java虚拟机的入口应用
经常使用方法:
方法名称
描述
getParent()
返回该类加载器的父类加载器
loadClass(String name)
加载名称为name的类,返回结果为java.lang.Class类的实例
findClass(String name)
查找名称为name的类,返回结果为java.lang.Class类的实例
findLoadedClass(String name)
查找名称为name的已经被加载的类,返回结果为java.lang.Class类的实例
defineClass(String name,byte[] b,int off,int len)
把字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例
resolveClass(Class<?> c)
链接指定的一个Java类
获取ClassLoader 实例的途径
代码演示:
public class ClassLoaderTest2 {
public static void main(String[] args) {
try {
//1.Class.forName().getClassLoader()
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader); // String 类由启动类加载器加载,咱们没法获取
//2.Thread.currentThread().getContextClassLoader()
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3.ClassLoader.getSystemClassLoader().getParent()
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
复制代码
Java虚拟机对class文件采用的是按需加载的方式,也就是说当须要使用该类时才会将它的class文件加载到内存生成class对象。并且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理(父类再向上委派,一直到启动类加载器),它是一种任务委派模式
举例:
本身创建一个java.lang.String类,若是此类加载 将打印 语句
package java.lang;
public class String {
static{
System.out.println("我是自定义的String类的静态代码块");
}
}
复制代码
在另外的程序中加载 String 类,上面的语句并无打印,默认加载就是 原生的String类
public class StringTest {
public static void main(String[] args) {
java.lang.String str = new java.lang.String();
System.out.println("hello,atguigu.com");
StringTest test = new StringTest();
System.out.println(test.getClass().getClassLoader());
}
}
复制代码
由于 当程序须要加载 java.lang.String 类时,类加载器将一直向上委托, 直到 启动器加载类,而启动器加载能够加载String 类,则为 原生java.lang下的String 类,这将保证原生api的安全性,不会由于用户环境重写的类,致使全部之前使用原生api的地方受到干扰
代码2
package java.lang;
public class String {
static{
System.out.println("我是自定义的String类的静态代码块");
}
//错误: 在类 java.lang.String 中找不到 main 方法
public static void main(String[] args) {
System.out.println("hello,String");
}
}
复制代码
运行时报错:
当加载main方法时, 加载其承载类String,一样委托到启动器加载类,致使加载成原生的String 类,而原生的String 类可没有 main方法
代码3
在自定义的java.lang 包下定义自定义的类
package java.lang;
public class ShkStart {
public static void main(String[] args) {
System.out.println("hello!");
}
}
复制代码
运行报错
由于报名为java.lang 属于启动类加载器的加载范畴, 出于对 启动类加载器的保护,jvm禁止 自定义的类被 启动类加载器加载,防止自定义类 对启动器加载器产生破坏,
经过上面的例子,咱们能够知道,双亲机制能够
当咱们使用 jdbc 等 第三方 实现类jar包时,使用到ClassLoader
自定义String类时:在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程当中会先加载jdk自带的文件(rt.jar包中java.lang.String.class),报错信息说没有main方法,就是由于加载的是rt.jar包中的String类。这样能够对java核心源代码的保护,保证java核心代码的运行环境绝对的独立,这就是沙箱安全机制
相关问题:
如何判断两个class对象是否相同?
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
类的主动使用和被动使用
Java程序对类的使用方式分为:主动使用和被动使用。主动使用,又分为七种状况:
除了以上七种状况,其余使用Java类的方式都被看做是对类的被动使用,都不会致使类的初始化,即不会执行初始化阶段(不会调用 clinit() 方法和 init() 方法)