3.JVM类加载机制

(1)类加载机制
虚拟机把描述类的数据从Class文件,用ClassLoader ,加载到内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机直接使用的java类型,
这就是虚拟机的类加载机制。

(2)在java语言里面,类型的加载、连接、初始化过程都是在程序运行期间完成的,
缺点:增加性能开销
优点:提高程序灵活性

(3)类的生命周期
a.加载
b.连接(包括:验证,准备,解析)
c.初始化
d.使用
e.卸载(出内存)
类的加载过程必须按照这五个步骤,按部就班的 开始 。

c.初始化
什么情况下,会开始加载?JVM规范并没有强制性约束,但是有五种情况,必须立刻对类进行初始化(加载连接必然要在初始化之前开始)

I. 遇到,new , getstatic , putstatic , invokestatic四条字节码指令时,如果没有进行过初始化,则需要先触发其初始化
这四条字节码指令使用场景:
new: 实例化对象
getstatic: 读取一个类的静态变量
putstatic: 设置一个类的静态变量
invokestatic: 调用一个类的静态方法的时候

II.使用java.lang.reflect包,对类进行反射调用的时候,如果类没有过初始化,则对其初始化。

III.当初始化一个类的时候,发现其父类还没有过初始化,需要初始化

IV.JVM启动时,main()方法所在的类

V.当使用JDK1.7的动态语言支持时,方法句柄对应的类没有过初始化,则对其初始化

(Test3继承Test2,当调用Test3的静态变量时,先初始化Test3; Test2也有静态变量a,Test3继承Test2 , 调用Test3.a时候,
只会初始化Test2,不会初始化Test3 , 至于会不会加载和连接Test3 ,取决于JVM的具体实现,Sun HotSpot虚拟机,
可通过XX:+TraceClassLoading观察到,此操作会导致子类的加载 )

Test2 [] test = new Test2[10]; 不会初始化Test2

c是Test的一个常量,在常量池里面,所以,调用Test2.c,也不会初始化Test2

当一个类引用了另一个类的常量时,编译时,会把被引用的常量,转化为引用者自己常量池中的常量,所以,编译完后,两个类就没有关系了

(4)类加载过程
a. 加载 ,是类加载(Class Loading)过程中的一个阶段。三个阶段:
I.通过一个类的全限定名,来获取此类的二进制字节流。
II.将字节流所代表的静态存储结构,转化为方法区的运行时数据结构。
III.在内存中生成一个代表这个类java.lang.Class对象,作为方法区这个类的各种数据访问入口。
(数组不是类加载器创建,是由JVM自己创建的。
对于HotSpot虚拟机而言,Class对象比较特殊,它虽然是对象,但是,存放在方法区里面。这个对象将作为,程序访问方法区中数据的外部接口。


b.连接
I.验证:确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机。
这个阶段是否严禁,直接决定了java虚拟机能否承受恶意代码的攻击。
只有通过了验证,字节流才会存入方法区,所以验证早于加载的存储。

II.准备:准备阶段是正式为类变量(static),分配内存,并设置变量的初始值,这些变量所使用的内存都将在方法区进行分配。
第一,这个时候进行内存分配的仅仅是类变量(static),不包括实例变量。实例变量将会在对象初始化时候分配在堆中。
第二,这里所说的初始值,通常情况下是类型0值,即public static int a = 123 , 此时只为a分配a=0。
第三,如果指定了public final static int a = 123,此时,为a初始化a=123。

III.解析:解析阶段是,虚拟机将常量池内的符号引用,替换为直接引用的过程。
解析动作主要针对 类、接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

c.初始化 , 初始化是类加载过程的最后一步,初始化阶段,才真正开始执行,类中定义的java程序代码。
I. 初始化,是执行类构造器clinit()方法的过程,而非类的构造方法。
II. client()方法,是编译器收集类中所有类变量(static) 的赋值动作,和静态方法块中的语句合并产生的。
编译器收集的顺序,是由语句在源文件中的顺序决定的。
III.静态语句块中,只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态块中可以赋值,但是不能访问。
static{
i = 0;//可以执行
System.out.print(i);// 会提示非法向前引用
static int i =1;
}
public Class Parent{
public static int A = 1;
static {
A = 2;
}
}

public Class Child extends Parent{
public static int B = A;
}

public class Test{
public static void main(String [] args){
System.out.println(Child.B); //
}
}

上面代码会打印2,因为clinit()方法执行顺序,由语句在源文件中的顺序决定的。

IV.clinti()方法,与类的构造函数不同,他不需要显示调用父类构造器,JVM会保证在子类的clinit方法执行前执行。
V.clinit()方法对于类或者接口来说不是必须的。如果一个类没有静态块,也没有赋值操作,就不用clinit()方法。

4.类加载器
(1)
JVM设计团队,把类加载阶段中“通过一个类的全限定名,来获取描述此类的二进制字节流” 这个动作,放到JVM外部,以便让
应用程序自己决定如何去获取所需要的类。实现这个动作的代码块,称为类加载器。
代码:
public static void main(String args []) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
ClassLoader myLoader = new ClassLoader() {
public Class<?> loadClass(String name) throws ClassNotFoundException{
try{
String fileName = name.substring(name.lastIndexOf(".")+ 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is == null){
return super.loadClass(name);
}

byte[] b = new byte[is.available()];
return defineClass(name , b , 0 , b.length);
}catch(IOException e){
throw new ClassNotFoundException(name);
}
}
};

Object obj = myLoader.loadClass("com.jvm.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof ClassLoaderTest);
}

代码构造了一个简单的类加载器,使用这个类加载器加载了一个名为"com.jvm.ClassLoaderTest" 的类,并实例化这个类的对象,
从第一句打印可以看出这个对象确实是com.jvm.ClassLoaderTest 实例化出来的。
但是,从第二句发现,这个对象对com.jvm.ClassLoaderTest 做所属类型检查的时候返回了false,这是因为在jvm中存在了两个 ClassLoaderTest类,
一个是由系统应用程序加载的,一个是由自定义类加载器加载的。
虽然他们来自同一个Class文件,但是,是两个独立的类。
所以,对于任何一个类,都需要由加载他的类加载器,和 这个类本身一同确定其在JVM中的唯一性,每一个类加载器都有一个独立的命名空间。
所以,比较两个类是否相等,只有在这两个类是否由同一个类加载器加载的前提下才有意义,否则,即使这两个类源自同一个Class文件,被同一个JVM加载,
只要加载他们的类加载器不同,那这两个类必定不相等。

(2)双亲委派模型
a.从JVM角度来看,只存在两种类加载器:
I.启动类加载器(Bootstrap ClassLoader) ,使用C++语言实现,是虚拟机自身的一部分。
II.所有其他类加载器,都由JAVA语言实现,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader

b.从开发人员的角度来看,会使用3中系统提供的类加载器

I.启动类加载器(Bootstrap ClassLoader),负责加载 JAVA_HOME\lib 目录中类库,加载到虚拟机内存中。
启动类加载器,无法被java程序直接使用。

II.扩展类加载器(Extension ClassLoader) ,负责加载 JAVA_HOME\lib\ext 目录中的类库

III.应用程序类加载器(Application ClassLoader)又称系统类加载器, 负责加载 javaBuildPath指定的类库,
如果程序中没有指定类加载器,一般情况下,这个就是程序中的类加载器。

VI.自定义类加载器。

c.类加载器关系
启动类加载器(Bootstrap ClassLoader)
^
|
|
扩展类加载器(Extension ClassLoader)
^
|
|
应用程序类加载器(Extension ClassLoader)
^
|
|
自定义类加载器(User ClassLoader)

d.这种层次关系模型,称为类加载器的双亲委派模型。在JDK1.2被引入
双亲委派模型,要求,除了顶层的 启动类加载器 之外,其余的类加载器,都应有自己的父类加载器。这里的父子关系一般不用继承,而是使用组合(Composition)
关系,来复用父类加载器的代码。

e.双亲委派模型,工作过程:
I.如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成。每一个层次类加载器都是如此。
II.当父类反馈自己无法完成这个加载请求时,子加载器才会尝试自己去尝试加载。

f.好处:JAVA类随着它的类加载器一起具备了,一种带有优先级的层次关系。
例如:java.lang.Object类,存放在rt.jar中,无论哪一个类加载器加载这个类,都是为派给最顶端的Bootstrap ClassLoader加载,因此Object类在程序各种
类加载器环境中都是同一个类。
相反:如果没有双亲委派模型,如果用户自己编写了称为java.lang.Object类,并放在程序的ClassPath中,那么,系统中将会出现多个不同的Object类,Java类型
体系中最基本的行为都无法保证。
综上,双亲委派模型,对于java程序的稳定性非常重要。

jvm--2.类加载机制的更多相关文章

  1. JVM内存结构 JVM的类加载机制

    JVM内存结构: 1.java虚拟机栈:存放的是对象的引用(指针)和局部变量 2.程序计数器:每个线程都有一个程序计数器,跟踪代码运行到哪个位置了 3.堆:对象.数组 4.方法区:字节流(字节码文件) ...

  2. JVM之类加载机制

    JVM之类加载机制 JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 类加载五部分 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这 ...

  3. JVM的类加载机制全面解析

    什么是类加载机制 JVM把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被JVM直接使用的Java类型,这就是JVM的类加载机制. 如果你对Class文件的结 ...

  4. 大白话谈JVM的类加载机制

    前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才 ...

  5. 一文教你读懂JVM的类加载机制

    Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...

  6. JVM的类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 类加载的过程: 包括加载.链接(含验证.准备 ...

  7. 【JVM】类加载机制

    原文:[深入Java虚拟机]之四:类加载机制 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 类加 ...

  8. 深入理解JVM(3)——类加载机制

    1.类加载时机 类的整个生命周期包括了:加载( Loading ).验证( Verification ).准备( Preparation ).解析( Resolution ).初始化( Initial ...

  9. (转) JVM——Java类加载机制总结

    背景:对java类的加载机制,一直都是模糊的理解,这篇文章看下来清晰易懂. 转载:http://blog.csdn.net/seu_calvin/article/details/52301541 1. ...

  10. JVM虚拟机—JVM的类加载机制

    1 什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

随机推荐

  1. LAMP环境配置 linux+apache+mysql+php

    虚拟机安装Linux系统: 新建虚拟机过程中选择Linux,下面选择centos或者是Ubuntu Linux切换图像命令:注意只有装了图像界面才可以切换 查看安装环境的版本: rpm -qa 查看安 ...

  2. [bzoj3207][花神的嘲讽计划Ⅰ] (字符串哈希+主席树)

    Description 背景 花神是神,一大癖好就是嘲讽大J,举例如下: “哎你傻不傻的![hqz:大笨J]” “这道题又被J屎过了!!” “J这程序怎么跑这么快!J要逆袭了!” …… 描述 这一天D ...

  3. Python的数据类型

    Python的主要数据类型有:Number(数字),String(字符串类型),布尔值,List(列表),Tuple(元组)和Dictionary(字典). 1.数字(Number) 数字包括整数和浮 ...

  4. codevs 1164 统计数字

    时间限制: 1 s  空间限制: 128000 KB  题目等级 : 白银 Silver 题目描述 Description [问题描述]某次科研调查时得到了n个自然数,每个数均不超过150000000 ...

  5. html drag api 在firefox 下 拖动出现新窗口的解决办法

    有个功能,需要用drag drop api 来做. 发现在firefox下拖放,会出现新的tab 页签,即使在ondragover.ondrop中使用了event.preventDefault也无济于 ...

  6. [LeetCode] Valid Perfect Square 检验完全平方数

    Given a positive integer num, write a function which returns True if num is a perfect square else Fa ...

  7. [LeetCode] Reverse Vowels of a String 翻转字符串中的元音字母

    Write a function that takes a string as input and reverse only the vowels of a string. Example 1:Giv ...

  8. 记录rewrite url我之前不知道的地方

    大部分url重写的需求是伪静态,当然有很多第三方开源组件,但是这种需求的核心方法其实就是context.rewritePath() 要是系统像ARR那样,用重写做代理和反向代理,一般的重写就不行了,c ...

  9. freemarker种种

    #include要使用绝对路径的话,在路径最前面加个"/"就行--<#include "/includes/v2/headerMacro.ftl"> ...

  10. <Script>放置位置

    html文件是自上而下的执行方式 css引入执行加载时,程序仍然往下执行 script脚本是则中断线程,待该script脚本执行结束之后程序才继续往下执行 页面效果实现类的js放在body之前,动作, ...