Java类加载机制(一)
类加载的过程
先从一个HelloWorld说起,对于一个HelloWorld.java文件,起初我们在dos命令行下面使用javac HelloWorld.java编译源程序,生成一个HelloWorld.class的字节码文件,然后我们使用javaHelloWorld就可以执行该程序,可是从我们硬盘上的.class文件是如何变成内存中的执行指令的呢?再细分的一点说,我们知道Java的宣传语:“一次编译,出处执行”,这是由于在应用程序和操作系统中间有一层是Java虚拟机,至于Java虚拟机和操作系统之间的交互,这里不深入讲解。那么我们的问题变成了:.class文件是先怎么被加载到Java虚拟机中的呢?
其实这主要通过Java类加载器完成的,JVM自带了3种类加载器,分别是根类加载器(Bootstrap)、扩展类加载器(Extension)、系统类加载器(SystemClassLoader/也叫做应用类加载器ApplicationClassLoader),另外用户也可以自定义自己的类加载器,如何自定义类加载器后面会说,它们的层次关系如下图所示。
类加载器的工作过程简单的说就是类加载器会将.class文件中的二进制数据读取到内存中,将其放入到JVM运行时数据区的方法区内,然后在java虚拟机的堆中创建一个java.lang.Class对象用来封装类在方法区内的数据结构。Java类加载的最终结果是生成了堆中的Class对象。补充一点:对于这个Class对象其实在编译时就已经存在,无论何时编译器在编译Java源文件的时候都会在编译后的字节码中嵌入一个public、static、final类型的字段class,这个字段表示java.lang.Class的一个实例,因为它是静态的public的,所以,很多时候我们可以通过类名.class来访问它。
详细过程分析:一个.class文件被加载到内存会进行如下的步骤:加载、连接、初始化
加载:就是类加载器ClassLoader查找并加载类的二进制数据到内存的方法区,并在虚拟机的堆中创建一个Class对象的实例,加载的方式可以是本地直接加载也可以是网络下载.class文件或者直接从jar、zip等归档文件中加载等等;
连接:具体可以分为验证、准备、解析三个步骤
a验证:确保被加载类的正确性,包括类的结构检查、语义检查和字节码检查等等;
b准备:为静态变量分配内存空间,并将其初始化,这里的初始化是赋予默认的初始值,如int型则赋予0,boolean型则赋予false;
c解析:将类中的符号引用转化为直接引用
初始化:为类的静态变量赋予正确的初始值,也就是赋予用户对其设置的初始值,和上面的初始化是不同的。
类被初始化时机:JVM只有等到一个类被主动使用时才会去初始化这个类,主动使用有以下6种情况:
1.创建类的实例(比如,通过new关键字创建实例)
2.访问某个类或者接口的静态变量。或者对其静态变量赋值;
3.调用类的静态方法
4.反射
5.初始化一个类的子类,也会初始化该类的父类
6.JVM启动时被标记为启动类的那些类
除了这6种,其他对类的使用都不是主动使用,不会导致类的初始化。
根据上述知识,来看一个类加载次序的例子:
public class TestClassLoader1 {
public static void main(String[] args) {
Count c = Count.getCount();
System.out.println(c.num1);
System.out.println(c.num2);
}
}
//定义一个内部类Count
class Count{
private static Count count = new Count(); //位置1
public static int num1;
public static int num2 = 0;
//private static Single single = new Single(); //位置2
Count(){
num1++;
num2++;
}
public static Count getCount(){
return count;
}
}
上述程序运行时,首先会执行main方法,在main方法中调用了Count类的静态方法getCount,所以会导致Count类被加载到内存,加载的时候在连接的准备阶段,静态变量就已经被赋予默认初始值(count=null,num1=0,num2=0),由于主动使用会导致类的初始化,所以还会将用户赋予的正确值赋予它们,程序顺序执行,首先给count变量复制,new关键字调用构造方法,num1和num2都自加了一次,都等于1,接着,num1用户没有对其赋值,不用初始化,num2用户对其赋值为0,所以num2又变为0。所以,程序最后输出的是1,0。上述程序,如果把位置1的语句移到位置2,程序的输出结果就将变成1,1,分析过程和上面是一致的。
对于上述主动使用类的第五种情况:初始化一个类的之类时,也会初始化它的父类,有下面的例子验证
import java.util.Random;
public class TestClassLoader2 {
public static void main(String[] args) {
System.out.println(T2.y);
}
}
class T1 {
public static int x = 1;
static{
System.out.println("T1 block");
}
}
class T2 extends T1{
public static int y = 1; //位置1
static{
System.out.println("T2 block");
}
}
分析:首先在初始化T2之前,会先初始化它的父类T1,输出“T1 block”,然后初始化T2,输出“T2 block”,最后输出main方法的值1,最终的输出结果就是:
T1 block
T2 block
1
对于含有final修饰的变量,如果在编译的时候可以确定其确切的值,则对其的访问不算对该类的主动使用,下面的例子可以验证这一点:
例1:还是上面的例子,把位置1的语句改为:
public static final int y = 1; //位置1
那么程序的输出结果为:1 .(分析:编译的时候y的值已经可以唯一的确定,不会改变的,实质就是一个常量,所以对其的访问不会导致T2类的初始化,也就不会导致 T1类的初始化)
例2:还是上面的例子,把位置1的语句改为:
public static final int y = new Random().nextInt(100); //位置1
那么程序的输出结果为:
T1 block
T2 block
76
原因就是对于静态变量y的值在编译期间不能确定,所以y然是一个静态变量,对其的访问会导致类T2、T1的的初始化。
例子3:当程序访问的静态变量和静态方法确实在当前类或接口中定义时,才可认为是对当前类或接口的主动使用.
public class TestClassLoader3 {
public static void main(String[] args) {
System.out.println(Child.x);
}
}
class Parent{
public static int x;
static{
System.out.println("Parent Block");
}
}
class Child extends Parent{
static{
System.out.println("Child Block");
}
}
程序的输出结果为:(原因是对Child的静态变量x的访问不是真正访问Child类的静态变量,而是其父类的静态变量,所以只会初始化其父类)
Parent Block
0
当JVM初始化一个类的时候,要求它的所有父类已经被初始化,但这条规则并不适用于接口。初始化一个类时,并不会初始化它所实现的接口;初始化一个接口时,也不会初始化它已实现的父接口,只有当程序首次使用特定接口的静态变量时,才会导致该接口的初始化。
分享到:
相关推荐
### Java 类加载机制详解 Java 类加载机制是Java运行时环境的一个核心组成部分,它负责将编译后的Java字节码加载到JVM中,并确保Java应用程序能够正确地运行。类加载机制不仅涉及到类的加载、验证、准备、解析和...
### 深入研究Java类加载机制 #### 一、Java类加载机制概述 Java类加载机制是Java程序运行的第一步,它对于理解Java虚拟机(JVM)的行为至关重要。类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等...
Java 类加载机制是Java平台的核心特性之一,它负责将类的字节码加载到Java虚拟机(JVM)中并转换为运行时的类对象。理解这一机制对于优化应用程序性能和解决类相关的错误至关重要。 首先,类加载的过程分为三个主要...
JAVA 类加载机制是Java平台核心特性之一,它关乎到程序的运行时环境和代码的动态加载。理解这一机制有助于开发者解决与对象创建、配置问题、应用程序发布等相关的问题。以下是关于JAVA 类加载机制的详细分析: 首先...
Java 类加载机制原理与实现 Java 类加载机制是 Java 虚拟机(JVM)的一部分,负责将编译后的 Java 字节码文件加载到 JVM 中,以便执行 Java 程序。类加载机制是 JVM 的核心组件之一,对 Java 程序的执行和安全性起...
### JAVA类加载机制与动态代理 #### 一、类加载机制 ##### 1.1 类加载的时机 类加载机制负责将描述类的数据从`.class`文件加载到内存,并进行必要的校验、转换解析和初始化,使之成为可以被Java虚拟机直接使用的...
Java类加载机制是Java平台核心特性之一,它负责将类的.class文件加载到JVM(Java虚拟机)中,使得程序能够运行。本篇主要基于“译 Java类加载机制(一、二)”的博客内容,深入探讨Java的类加载过程、类加载器以及...
Java 类加载机制是Java运行时环境中的核心组成部分,它负责将`.class`文件中的字节码转换为运行时的Class对象。在这个过程中,类加载器扮演着至关重要的角色。本篇文章将深入探讨Java的双亲模型类加载机制,以及如何...
Java类加载机制是Java程序运行的关键部分,它使得程序能够在运行时动态加载和执行。类加载涉及多个步骤,包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化...
Java类加载机制详解 Java类加载机制是Java虚拟机(JVM)中的一种机制,负责将类从字节码文件加载到内存中,并将其转换为可执行的类对象。在Java中,类加载机制是通过ClassLoader来完成的,该机制在JDK 1.2以后变得...
Java类加载机制是Java平台的一个重要特性,它负责将类的字节码文件加载到JVM中,并在内存中创建对应的类对象。类加载机制涉及类加载顺序、类加载器的体系结构、类加载过程以及双亲委派模型等核心概念。架构师或高级...
Java类加载机制是Java运行时环境中的核心组成部分,它负责将编译后的字节码(.class文件)加载到Java虚拟机(JVM)中,使得程序能够执行。这一过程涉及多个步骤,包括加载、验证、准备、解析和初始化。理解类加载...
### Java 类加载机制详解 #### 一、引言 Java 类加载机制是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译后的字节码文件(.class)加载到内存中,形成Class对象,以便于Java程序能够识别并使用这些类。深入...
Java类加载机制是Java虚拟机(JVM)的核心特性,它负责在程序运行时找到并加载所需的类。在E-learning平台的功能模块更新中,这一机制起着至关重要的作用,因为它允许平台在不影响其他功能模块正常运行的情况下动态...
该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
详解JAVA类加载机制 JAVA类加载机制是JAVA虚拟机(JVM)的一部分,它负责将.class文件加载到内存中,并对其进行校验、转换、解析和初始化,以形成可以被JVM直接使用的JAVA类型。下面将详细介绍JAVA类加载机制的相关...