`
l.in
  • 浏览: 1576 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

JVM-类的加载器深入剖析

    博客分类:
  • JVM
阅读更多
Java类加载器深入剖析

本文章来源于 网络教学视频内容。

在如下的几种情况下,Java虚拟机将结束生命周期:

-1.执行了System.exit()方法
-2.程序正常执行结束
-3.程序在执行过程中遇到了异常或者错误而异常终止
-4.由于操作系统出现错误而导致Java虚拟机进程终止



类的加载、链接与初始化
-加载:查找并加载类的二进制数据

-详细介绍:
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构(Class对象只有唯一的一个,用于反射)
-加载.class文件方式
-1.从本地系统中直接加载
-2.通过网络下载.class文件(java.net.URLClassLoader)
-3.从zip,jar等归档文件中加载.class文件
-4.从专有数据库中提取.class文件
-5.将Java源文件动态编译为.class文件

-Java程序 调用Class对象的方法(比如newInstance()方法),将会在堆区中查找描述该实例化类的Class对象,然后再方法区中查找该实例化类的数据结构。

-类的加载的最终产品是位于堆区中的Class对象。

-Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法内的数据结构的接口。

-类加载器

 

详细介绍:

 

从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当Java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才有加载器loader本身加载Sample类。

 

-1.Java虚拟机自带的加载器(以下这三种加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。,)

*1.根类加载器 Bootstrap(C++语言写的,程序员在Java代码中无法获得该类,即出现这种情况:如果这个类如果是由根类加载器加载的,使用getClassLoader()方法时,将会返回空值。)

-详细介绍

该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。根类加载器从系统属性sun.boot.class.path所指定的目录加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,他并没有继承java.lang.ClassLoader类。

*2.扩展类加载器 Extension(Java语言写的)

-详细介绍

它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。

*3.系统类加载器(应用加载器) System(Java语言写的)

-详细介绍

它的父加载器为扩展类加载器。他从环境变量classpath或者系统属性java.class.path所制定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。

-2.用户自定义的类加载器(所有用户自定义的类加载器应该继承java.lang.ClassLoader类)
*1.java.lang.ClassLoader的子类
*2.用户可以定制类的加载方式

 

package com.lin.test;

import org.junit.Test;

/** 
 * @author Irving 
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 上午10:07:01
 * 摘要:加载器的getClassLoader()方法返回值的测试
 */
public class Test1 {
	@Test
	public void testGetClassLoader()throws Throwable{
		Class<?> clazz=Class.forName("java.lang.String");
		System.out.println(clazz.getClassLoader());
		Class<?> c=Class.forName("com.lin.test.C");
		System.out.println(c.getClassLoader());
	}
}

class C{}



返回值:

 

null

sun.misc.Launcher$AppClassLoader@500c05c2

注:第一个为根类加载器,第二个为系统(应用)加载器

类加载的父亲委托机制

-优点

父亲委托机制能够提高软件系统的安全性。在此机制下,用户自定义的类加载器不可能应该有父加载器的可靠类,从而防止不可靠甚至恶意的代码代替有父加载器加载的可靠代码。例如:java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object类。

 

Class sampleClass=ClassLoader.getSystemClassLoader().loadClass("Sample");
系统类加载器首先从自己的命名空间中查找Sample类是否已经被加载,如果已经加载就直接返回代表Sample类的Class对象的引用。

 

如果Sample还没有被加载,系统类加载器请求扩展类加载器代为加载,扩展类加载器再请求根类加载器代为加载。若根类加载器和扩展加载器都不能加载,则系统类加载器尝试加载,若能加载成功,则成功将Sample类加载进虚拟机。若系统类加载器不能加载Sample类。表示所有父加载器以及系统类加载器都不能加载,则抛出ClassNotFoundException异常。

若有一个类加载器能成功加载Sample类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象的引用的类加载器(包括定义类加载器)都被称为初始类加载器

 

当生成一个自定义的类加载器实例,如果没有指定它的父加载器,那么系统类加载器就将成为该类加载器的父加载器。

 

JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)

如果这个类一直没有被程序主动使用,那个类加载器就不会报告错误。

类加载器命名空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能出现类的完整名字(包括类的包名)相同的两个类。

运行时包

运行时包由同一类加载器加载的属于相同包的类组成。

决定两个类是否属于同一个运行时包因素:

-1.定义类加载器是否相同

-2.同一运行时包名是否相同

安全性考虑

只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。

例如:用户自定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

创建用户自定义的类加载器

要创建用户自己的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数制定类的名字,返回对应的Class对象的引用。

-链接

-详细介绍

类被加载后,就进入连接阶段。链接就是将已经读入到内存中的类的二进制数据合并到虚拟机中的运行环境中去。(进行有机的整合)

 

-验证:确保被加载的类的正确性

-类的验证内容(出于安全性考虑,防止恶意用户手动制作字节码文件)

-1.类文件的结构检查:确保类文件遵从Java类文件的固定格式

-2.语义检查:确保类本身符合Java语言的语法规定,比如验证final类型的类没有子类,以及final类型的方法没有被覆盖

-3.字节码验证:确保字节码可以被Java虚拟机安全的执行。字节码流代表Java方法(包括静态方法和实例方法),它是由被称作操作码的单字节指令组成的序列,每一个操作码后都跟一个或多个操作数。字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数

-4.二进制兼容性验证:确保相互引用的类之间协调一致。例如:在Worker类的gotoWork()方法会调用Car类的run()方法。Java虚拟机在验证Worker类时,会检查在方法区是否存在Car类的run()方法,假如不存在(当Worker类和Car类的版本不兼容,就会出现这种问题),就会抛出NoSuchMethodError错误。

 

-准备:为类的静态变量分配内存,并将其初始化为默认值。不为类的实例变量分配任何内存

-详细介绍

在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如:对于以下的Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间。

 

public class Sample{
	private static int a=1;
	private static long b;
}


 

-解析:把类中的符号引用转换为直接引用

-详细介绍

在解析阶段,Java虚拟机会把类的二进制数据中的符号引用替换为直接引用。

例如:在Worker类的gotoWork()方法中会引用Car类的run()方法。

 

public void gotoWork(){
	car.run();//这段代码在Worker类的二进制数据中表示为符号引用
}

在Worker类的二进制数据中,包好了一个对Car类的run()方法引用,它由run()方法的全名和相关描述组成。在解析阶段,Java虚拟机会把这个符号引用替换为一个指针,该指针指向Car类的run()方法内的内存位置,这个指针就是直接引用。

 

-初始化:为类的静态变量赋予正确的初始值,仍然不存在实例变量的分配内存。

-详细介绍

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:

-1.在静态变量的声明处进行初始化;

-2.在静态代码块中进行初始化。

class Sample{
	private static int a=1;//在静态变量的声明处进行初始化
	private static long b;
	static{
		a=2;//在静态代码块进行初始化
	}
}

package com.lin.test;


import org.junit.Test;


/**
* @author lin
* @E-mail:837929286@qq.com
* @version 创建时间:2014年11月10日 上午10:07:01
*/
public class Test1 {
@Test
public void test1(){
           Singleton singleton=Singleton.getInstance();
           System.out.println("counter1= "+singleton.counter1);
           System.out.println("counter2= "+singleton.counter2);
}
}




class Singleton{
//静态变量都是从上到下顺序初始化
//Singleton类返回一个静态实例引用赋值给singleton,Singleton调用私有的构造方法,将counter1和counter2自增1。
private static Singleton singleton=new Singleton();
//调用Singleton类的静态变量,并将其初始化。由于counter1没有初始值,而counter2则被赋予初始值0
public static int counter1;
public static int counter2=0;

private Singleton(){
         counter1++;
         counter2++;
}
public static Singleton getInstance(){
        return singleton;
}
}

输出结果:

counter1= 1
counter2= 0

分析:将Singleton类进行构造方法私有化,防止构造新实例。在静态实例化类中,singleton被赋值为null,counter1和counter2被赋值为0。

在编译常量时,如果常量在编译时无法确定的,则初始化整个类。否则,则先初始化常量,而不初始化类。

 

package com.lin.test;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test2 {
	@Test
	public void testFinal(){
		System.out.println(FinalTest.x);
	}
}
class FinalTest{
	public static final int x=6/3;
	static{
		System.out.println("FinalTest static block");
	}
}

输出结果:

 

2

 

package com.lin.test;

import java.util.Random;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test3 {
	@Test
	public void testFinal(){
		System.out.println(FinalTest.x);
	}
}
class FinalTest{
	public static final int x=new Random().nextInt(100);
	static{
		System.out.println("FinalTest static block");
	}
}

输出结果:

 

FinalTest static block
2

-类的初始化步骤

-1.假如这个类还没有被加载和链接,那就先进行加载和链接

-2.假如类存在直接的父类,并且这个父类还没有初始化,那就先初始化直接的父类

-3.假如类中存在初始化语句,那就依次执行这些初始化语句

 

package com.lin.test;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test4 {
	static{
		System.out.println("Test4 static block");
	}
	@Test
	public void testChild(){
		System.out.println(Child.b);
	}
}
class Parent{
	static int a=3;
	static{
		System.out.println("Parent static block");
	}
}
class Child extends Parent{
	static int b=14;
	static{
		System.out.println("Child static block");
	}
}
输出结果:

Test4 static block
Parent static block
Child static block
14

-类的初始化时机:

 

当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

-1.在初始化一个类时,并不会先初始化它所实现的接口。

-2.在初始化一个接口时,并不会先初始化它的父接口。

因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量,才会导致该接口的初始化。

 

程序中子类的“主动使用”会导致父类被初始化;但对父类的主动使用并不会导致子类初始化。(不可能Object类的对象主动使用,导致整个系统的子类的所有对象都被初始化)。如下例所示:

 

package com.lin.test;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test5 {
	static{
		System.out.println("Test5 static block");
	}
	@Test
	public void testChild(){
		Parent parent;//不符合主动使用的情况,将不会被初始化
		System.out.println("--------------------");
		
		parent=new Parent();//符合主动使用的情况,将被初始化
		System.out.println(Parent.a);
		System.out.println(Child.b);
	}
}
class Parent{
	static int a=4;
	static{
		System.out.println("Parent static blcok");
	}
}
class Child extends Parent{
	static int b=2;
	static{
		System.out.println("Child static block");
	}
}

输出结果:

 

Test5 static block
--------------------
Parent static block
4
Child static block
2


*Java程序对类的使用方式可分为两种:

-1.主动使用
-2.被动使用



*所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们。


下面有六种主动使用的情况:

-1.创建类的实例

-2.访问某个类或接口的静态变量,或者对该静态变量赋值

 

package com.lin.test;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test2 {
	@Test
	public void testFinal(){
		System.out.println(FinalTest.x);
	}
}
class FinalTest{
	public static final int x=6/3;
	static{
		System.out.println("FinalTest static block");
	}
}
输出结果:

 

 

2

 

package com.lin.test;

import java.util.Random;

import org.junit.Test;

/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
public class Test3 {
	@Test
	public void testFinal(){
		System.out.println(FinalTest.x);
	}
}
class FinalTest{
	public static final int x=new Random().nextInt(100);
	static{
		System.out.println("FinalTest static block");
	}
}
输出结果:

 

FinalTest static block
2


-3.调用类的静态方法

-4.反射(Class.forName(""));如下:

 

package com.lin.test;
/** 
 * @author Irving
 * @E-mail:c837929286@gmail.com
 * @version 创建时间:2014年11月10日 下午9:36:53
 */
class CL{
	static{
		System.out.println("Class CL");
	}
}
public class Test6 {
	public static void main(String[] args)throws Exception{
		//获得系统类加载器
		ClassLoader loader=ClassLoader.getSystemClassLoader();
		//调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
		Class<?> clazz=loader.loadClass("com.lin.test.CL");
		System.out.println("---------------------------");
		//利用反射,将会导致类的主动使用
		clazz=Class.forName("com.lin.test.CL");
	}
}
输出结果:
---------------------------
Class CL

 

 

-5.初始化一个类的子类

-6.Java虚拟机启动时被标明为启动类的类

 

除了以上六种情况之外,其他情况都被看作是对类的被动使用,都不会导致类的初始化。

分享到:
评论

相关推荐

    深入Java虚拟机_002_深入详解JVM之类加载器深度剖析、根、扩展及系统类加载器

    本部分我们将深入探讨JVM中的类加载器,特别是根类加载器、扩展类加载器和系统类加载器。 首先,让我们了解类加载的基本过程。当JVM启动时,会触发类加载。这个过程分为三个阶段:加载、链接和初始化。加载阶段,类...

    JAVA-JVM-01类加载机制

    本文将深入剖析Java中的类加载器和双亲委派机制,并通过示例讲解如何自定义类加载器。 类加载过程是Java程序启动和运行的关键步骤。首先,Java源文件被编译成字节码文件,然后被打包成JAR文件。在运行程序时,我们...

    01-VIP-类加载机制深度剖析(1)1

    本篇将深入探讨类加载的过程、类加载器以及双亲委派机制。 一、类加载过程 1. **加载**:当程序需要使用一个类时,JVM会首先检查该类是否已经被加载。如果尚未加载,类加载器会从硬盘上的.class文件或者jar包中...

    从JDK源码级别剖析JVM类加载机制

    JVM使用类加载器(ClassLoader)来完成这一任务。在Java中,每个类都由一个对应的ClassLoader实例负责加载。默认的类加载器包括Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)和...

    jvm源码解读以及jvm调优,看的过程中会把c-c++文件也会放到里面进行解读-jvm-original.zip

    - 类装载器:理解类装载的过程,包括加载、验证、准备、解析和初始化等步骤,有助于了解类如何在JVM中被动态地加载和使用。 - 运行时数据区:深入分析堆内存管理,如垃圾收集机制(如分代收集、标记-清除、复制...

    JVM-3rd.7z

    这本书深入剖析了JVM的工作原理,涵盖了JVM的内存管理、类加载机制、垃圾回收、性能优化等多个重要主题,是Java开发者深入了解JVM不可或缺的参考文献。以下将基于书名和描述中的关键词,对JVM的相关知识点进行详细的...

    jvm-jit-examples:JVM JIT 和 CHA 演示代码示例

    它不仅负责类的加载、验证、解析和初始化,还在运行时通过JIT(Just-In-Time)编译器优化性能。本篇将围绕"jvm-jit-examples"项目,探讨JVM JIT编译器的工作原理以及CHA(Class Hierarchy Analysis)的作用,并通过...

    实战JAVA虚拟机 JVM故障诊断与性能优化.pdf

    - 类加载机制:JVM通过类加载器将.class文件加载到内存中,包括启动类加载器、扩展类加载器和应用程序类加载器。 - 方法区:存储已加载类的信息,如常量、静态变量等。 - 堆内存:所有对象实例都在堆中分配内存,...

    深入剖析JVM架构及其面试考点详析

    内容概要:本文档全面讲解了JVM(Java Virtual Machine)的关键概念和技术细节,特别是围绕JVM的各主要组件如类加载器、运行时数据区、执行引擎、垃圾回收机制等方面进行了详细介绍。此外,文档列举了大量的面试题,...

    Java类加载内幕

    本文旨在深入剖析 Java 类加载的过程,帮助读者理解类加载的基本概念及其在 Java 虚拟机 (JVM) 中的实现方式。 #### 二、基础知识 **类与数据的区别:** 在 Java 中,类代表执行的代码,而数据则表示与这些代码相...

    jvm调优实用工具.rar

    在类加载方面,它能展示每个类的加载数量、大小以及类加载器的使用情况,这对于理解和优化类的加载机制非常有帮助。 线程是Java多线程编程的核心,JProfiler可以实时显示线程状态,包括运行、等待、阻塞等,这有助...

    深入了解jvm(Inside java virture machine)

    《深入了解JVM(Inside Java Virture ...《Inside Java Virtual Machine》一书深入剖析了JVM的各个方面,结合博文学习,你将能够对JVM有更全面、深入的认识,从而更好地驾驭Java编程,提升软件开发的效率和质量。

    Java零基础 - Java的加载与执行原理剖析.md

    - **加载**:JVM通过类加载器加载字节码文件,并创建一个对应的Class对象。 - **验证**:JVM对加载的字节码进行验证,确保字节码符合Java语言规范和安全要求。这一步非常重要,因为它保证了字节码的安全性和正确性...

    深入理解JVM

    虽然描述部分的信息重复,但从标题可以看出,文章的主要目的是围绕Java虚拟机(JVM)进行深入剖析。接下来,我们将详细阐述JVM的基本概念、架构组成以及其内部运作机制等关键知识点。 ### Java虚拟机(JVM)基本概念 ...

    《深入剖析 Tomcat》PDF版本下载.txt

    根据提供的文件信息,本文将对《深入剖析 Tomcat》这一资料进行详细的知识点解析。Tomcat作为一款开源的Servlet容器,被广泛应用于Java Web应用程序的部署与运行环境中。本资料旨在帮助读者深入了解Tomcat的工作原理...

    深入理解Java虚拟机JVM高级特性与最佳实践1

    在类加载机制方面,作者解释了JVM是如何在运行时动态加载和链接Java类的,包括双亲委派模型、类加载器的种类以及自定义类加载器的设计与实现。理解这些机制对于构建灵活的应用程序架构至关重要,尤其是在处理热部署...

    jvm知识点总览(类的加载机制+内存结构+GC算法 垃圾回收+GC分析 命令调优)

    类加载器是实现类加载的关键组件,它们分为三种:启动类加载器(Bootstrap)、扩展类加载器(Extension)和应用类加载器(Application)。类加载器遵循全盘负责、父类委托和缓存机制的原则,形成了一种层次化的结构...

    JVM培训ppt

    类加载器包括启动类加载器、扩展类加载器和应用类加载器等,它们遵循双亲委派模型。 2. **运行时数据区**: 包括方法区、堆、虚拟机栈、本地方法栈和程序计数器。其中,方法区存储类信息,堆存储对象实例,虚拟机栈...

    深入理解jvm

    2. **内存管理**:这部分内容会深入剖析JVM的内存模型,包括堆、栈、方法区、本地方法栈等,以及它们各自的作用。特别是对Java内存模型(JMM)的讲解,有助于理解线程间的共享变量访问规则。 3. **类加载机制**:书...

    JVM的升入剖析

    本文将对JVM进行深入剖析,展示其如何在幕后支持Java程序的运行。 JVM主要由四个部分组成,分别是Java编程语言、类文件格式、虚拟机本身以及丰富的API。Java语言编写出的源代码,通过编译器生成字节码文件(.class...

Global site tag (gtag.js) - Google Analytics