欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JVM核心教程之JVM运行与类加载全过程详解

程序员文章站 2023-12-01 08:38:58
为什么要使用类加载器? java语言里,类加载都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会给java应用程序提供高度的灵活性。例如:...

为什么要使用类加载器?

java语言里,类加载都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会给java应用程序提供高度的灵活性。例如:

1.编写一个面向接口的应用程序,可能等到运行时再指定其实现的子类;

2.用户可以自定义一个类加载器,让程序在运行时从网络或其他地方加载一个二进制流作为程序代码的一部分;(这个是android插件化,动态安装更新apk的基础)

为什么研究类加载全过程?

  • 有助于连接jvm运行过程
  • 更深入了解java动态性(解热部署,动态加载),提高程序的灵活性

类加载机制

jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成jvm可以直接使用的java类型的全过程。

JVM核心教程之JVM运行与类加载全过程详解

加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。

JVM核心教程之JVM运行与类加载全过程详解

链接

将java类的二进制代码合并到jvm的运行状态之中的过程

  • 验证: 确保加载的类信息符合jvm规范,没有安全方面的问题
  • 准备: 正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法去中进行分配
  • 解析: 虚拟机常量池的符号引用替换为字节引用过程

初始化

  • 初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
  • 当范围一个java类的静态域时,只有真正声名这个域的类才会被初始化

例1:

public class demo01 {
 public static void main(string[] args) {
  a a = new a();
  system.out.println(a.width);
 }
}

class a{
 public static int width=100; //静态变量,静态域 field
 static{
  system.out.println("静态初始化类a");
  width = 300 ;
 }
 public a() {
  system.out.println("创建a类的对象");
 }
}

分析:

JVM核心教程之JVM运行与类加载全过程详解

说明:

内存中存在栈、堆(放创建好的对象)、方法区(实际也是一种特殊堆)

1、jvm加载demo01时候,首先在方法区中形成demo01类对应静态数据(类变量、类方法、代码…),同时在堆里面也会形成java.lang.class对象(反射对象),代表demo01类,通过对象可以访问到类二进制结构。然后加载变量a类信息,同时也会在堆里面形成a对象,代表a类。

2、main方法执行时会在栈里面形成main方法栈帧,一个方法对应一个栈帧。如果main方法调用了别的方法,会在栈里面挨个往里压,main方法里面有个局部变量a类型的a,一开始a值为null,通过new调用类a的构造器,栈里面生成a()方法同时堆里面生成a对象,然后把a对象地址付给栈中的a,此时a拥有a对象地址。

3、当调用a.width时,调用方法区数据。

当类被引用的加载,类只会加载一次

类的主动引用(一定会发生类的初始化)

  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当虚拟机启动,java demo01,则一定会初始化demo01类,说白了就是先启动main方法所在的类
  • 当初始化一个类,如果其父类没有被初始化,则先初始化它父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声名这个域的类才会被初始化
  • 通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类的引用,不会触发此类初始化
  • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

例2:

public class demo01 {
 static{
  system.out.println("静态初始化demo01");
 }
 
 
 public static void main(string[] args) throws exception {
  system.out.println("demo01的main方法!");
  system.out.println(system.getproperty("java.class.path"));
  
  //主动引用
//  new a();
//  system.out.println(a.width);
//  class.forname("com.sinosoft.test.a");
  
  
  //被动引用
//  system.out.println(a.max);
//  a[] as = new a[10];
  system.out.println(b.width);//b类不会被加载
  
 }
}

class b extends a {
 static {
  system.out.println("静态初始化b");
 }
}

class a extends a_father {
 public static int width=100; //静态变量,静态域 field
 public static final int max=100; 
 
 static {
  system.out.println("静态初始化类a");
  width=300;
 }
 public a(){
  system.out.println("创建a类的对象");
 }
}

class a_father extends object {
 static {
  system.out.println("静态初始化a_father");
 }
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。