热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java类加载机制

 jvm进程执行一个java程序,实质是调用jdk\bin下的java.exe,把这个java程序作为参数传递给java.exe,此命令会启动一个jvm进程,不管这个java程序有

 


jvm进程

执行一个java程序,实质是调用jdk\bin下的java.exe,把这个java程序作为参数传递给java.exe,此命令会启动一个jvm进程,不管这个java程序有多少个线程、有多复杂,这个java程序中所有的线程、变量都处于此jvm进程中,它们使用的都是此jvm进程的内存区。

当出现以下情况时,jvm进程会被终止:



  • 程序执行完毕,正常结束

  • 程序中调用system.exit()或runtime.getruntime().exit()退出jvm,结束程序

  • 发生错误或有未捕获、未处理的异常抛到了jvm

  • jvm进程被os强制结束,比如打开任务管理器,强制结束jvm

当jvm进程结束后,os会回收此进程占用的内存,java程序在内存中所有的数据都会丢失。

不同的jvm进程是相互独立的,不同享内存(数据)。比如先运行一遍,再运行一遍,后一遍使用的不是前一遍终止时的数据,而是从头开始的。

 

 

 

 


类加载

类加载也叫类初始化,指的是:当程序要使用某个类时,如果该类还没有被加载到内存中,jvm会通过加载、连接、初始化三个步骤来对该类进行初始化。

我们习惯把这三个步骤作为一个整体,称为类加载或类初始化。

 


1、加载

加载指的是:jvm将该类的.class文件读到内存中,并为之创建一个java.lang.class对象。

类是对对象的抽象,java.lang.class是对类的抽象,jvm中所有的类都是java.lang.class的实例。

 

加载由类加载器来完成,类加载器的任务是将类的.class文件读到内存中,并为之创建一个对应的java.lang.class对象。

jvm提供了3种类加载器:



  • bootstrap  classloader :根加载器,也叫做引导类加载器、原始类加载器,负责加载jdk中的核心类,这些核心类很多都是jvm运行所需要的,是jvm本身的一部分。根加载器是使用c++写的,不是继承自java.lang.classloader,不能直接引用。

  • extension  classloader :扩展类加载器,负责加载jdk中的扩展类(非核心类),由java编写,继承自classloader

  • system  classloader:系统类加载器,负责加载第三方的jar包、我们自己写的类,由java编写,继承自classloader

 

我们可以继承classloader类来自定义类加载器。classloader类的常用方法:



  • loadclass(string name,boolean resolve)    //根据名称来加载类,返回对应的java.lang.class对象

  • findclass(string name)     //根据名称来查找类

 

加载顺序:根加载器、扩展加载器、系统加载器、自定义的类加载器

 

jvm的类加载机制主要有3种:



  • 全盘负责。使用一个类加载器加载某个类时,这个类所依赖、引用的类也都由此类加载器加载。

  • 父类委托。先使用父类加载器来加载该类,如果父类加载器加载不了,才使用自身来加载。

  • 缓存机制。jvm会缓存所有加载过的class,当程序要使用某个类时,类加载器先在缓存中搜索是否有该class,有就直接使用,不再加载,没有才加载。所以运行程序过后,如果修改了某个要使用的类,需要重启jvm才会生效,不然使用的是之前缓存的class。

 

加载的大致步骤:


 

如果父类加载器不存在,说明当前加载器是根加载器,那就用根加载器来加载。

加载时,会找到此类对应的.class文件,根据.class文件来创建class对象,创建成功就返回class对象,创建失败则进行下一步处理,如果没有下一步处理,则抛出classnotfoundexception,因为默认代码是没问题的,加载失败是因为.class文件没有找到。

 

加载后,生成的class对象用全类名唯一标识。

一个类只加载一次,因为第一次加载此类过后缓存中会有此类的class对象,以后加载此类时直接从缓存中获取class对象,不再重新加载。

类加载是按需加载,程序要使用此类时才会加载。

类加载会自动加载依赖、引用的其他类,比如父类。

 

 

 


2、连接

加载获得的class对象是二进制数据,连接是把class对象合并到jre中(把class对象连接到jre)。jre即java runtime environment,java运行时环境。

连接分为3个阶段:



  • 验证:检验被加载的类的内部结构是否正确、和其他类是否协调一致

  • 准备:为类的成员变量分配内存,并设置默认的初始值

  • 解析:将类的二进制数据(class对象)中的符号引用替换为直接引用

class对象是加载时生成的,此时class对象中的成员变量还没分配内存,不知道该成员变量在内存中的地址,只能用符号来暂时表示该成员变量在内存中的地址。

准备阶段给成员变量分配内存以后,有了内存地址,下一步自然就是用内存地址(直接引用)替换掉这些符号。

 

 

 


3、初始化

初始化指的是:jvm对类进行初始化。注意是对类进行初始化,不是对类的对象进行初始化,所以类初始化只执行stati成员(类所有),不初始化普通成员变量。

初始化执行的static成员包括:



  • 设置了初始值的static成员变量,比如 private static int a=1;

  • 静态代码块static{ //...... }

如果某个static成员变量使用了final修饰,并且设置了初始值(常量),则在编译时就能确定这个final static成员变量的值,编译时会用其值(常量)来替换所有出现这个final static成员变量的地方,这个final static成员变量相当于常量。类初始化时不再初始化这个final static成员变量。

 

 

 


jvm加载|初始化一个类的完整步骤:



  • 如果缓存中没有这个类的class对象(没有被加载过),则先加载、连接这个类。

  • 如果这个类的直接父类还没有被初始化,则先初始化这个类的直接父类(会递归初始化所有祖先类)。

  • 如果这个类中有初始化语句(赋了初值的静态成员变量、静态代码块),则依次执行这些初始化语句。

当jvm初始化一个类时,会保证这个类的所有父类(直接父类+间接父类)都已经初始化了,因为要从父类继承成员,如果不先初始化父类,那继承什么?所以jvm最先初始化的总是java.lang.object类。

 

类加载|初始化并不局限于类,还包括接口。

 

 

 

 


jvm加载|初始化一个类的时机(jvm什么时候加载|初始化一个类):



  • 创建类的实例。包括通过new来创建、通过反射来创建、通过反序列化来创建。

  • 通过类名直接调用类的静态成员

  • 要初始化这个类的子类之前

  • 通过java.exe来运行某个主类,jvm会先初始化这个主类,再执行这个主类

 

通过反射可以创建对象(类的实例):

1 class studentclass = class.forname("test.student"); //必须写成全类名
2 constructor cOnstructor= studentclass.getconstructor(int.class, string.class, int.class, int.class);
3 object obj = constructor.newinstance(1, "chy", 20, 100);
4 student student = (student) obj;

第一句 class.forname("test.student") 就是加载、初始化指定的类,返回指定类的class对象。因为可能多个包下都有这个类,所以必须写成全类名,唯一标识这个类。

 

类的加载、初始化只是把类(.class文件)装入内存,生成一个class对象,这个class对象具有此类的实例的公共部分(因为初始化了static成员),普通成员变量也都分配了内存,设置为该种数据类型的默认值。这个class对象相当于这个类的实例的模板,jvm用这个模板来生产类的实例。

 

并不是说加载、初始化类之后,就得到了这个类的对象,初始化的是类,不是这个类的某个对象。

 

 

 

 


jvm创建对象的过程

以 new student(1,"chy", 20, 100) 的执行过程为例:

1、加载|初始化student类,得到student的class对象

class.forname("test.student"); //返回class对象

 

 

2、根据实参表的数据类型,通过class对象获取对应的构造器

class对象.getconstructor(int.class, string.class, int.class, int.class); //返回构造器

jvm中并没有什么int类、string类,jvm中存在的是这些类的class对象,所以要写成.class的形式,表示这个类的class对象。

 

 

3、使用获取到的构造器,传入实参值,创建类的实例

构造器.newinstance(1, "chy", 20, 100); //返回student类的实例

 



推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文节选自《NLTK基础教程——用NLTK和Python库构建机器学习应用》一书的第1章第1.2节,作者Nitin Hardeniya。本文将带领读者快速了解Python的基础知识,为后续的机器学习应用打下坚实的基础。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 我有一个从C项目编译的.o文件,该文件引用了名为init_static_pool ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • oracle c3p0 dword 60,web_day10 dbcp c3p0 dbutils
    createdatabasemydbcharactersetutf8;alertdatabasemydbcharactersetutf8;1.自定义连接池为了不去经常创建连接和释放 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文介绍了如何利用 `matplotlib` 库中的 `FuncAnimation` 类将 Python 中的动态图像保存为视频文件。通过详细解释 `FuncAnimation` 类的参数和方法,文章提供了多种实用技巧,帮助用户高效地生成高质量的动态图像视频。此外,还探讨了不同视频编码器的选择及其对输出文件质量的影响,为读者提供了全面的技术指导。 ... [详细]
author-avatar
倾其h所有只为爱你
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有