描述一下JVM加载class文件的原理机制

 

 

Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

 

类装载方式,有两种 :

1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,

2.显式装载, 通过class.forname()等方法,显式加载需要的类

 

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

 

Java的类加载器有三个,对应Java的三种类:

 

Bootstrap Loader  :启动类加载器,是虚拟机自身的一部分。负责将存放在\lib目录中的类库加载到虚拟机中。其无法被Java程序直接引用。 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)

             

ExtClassLoader   : 负责加载扩展类(就是继承类和实现类)

                           

AppClassLoader   :负责加载用户类路径(ClassPath)上所指定的类库(程序员自定义的类)

 

JVM中类的加载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化

类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

当类被加载后就进入连接阶段,这一阶段包括

验证:为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备:为静态变量分配内存并设置默认的初始值。

解析:将符号引用替换为直接引用。

最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:启动类加载器(BootStrap)、扩展类加载器(Extension)、应用程序类加载器(Application)。      

从Java 2(JDK 1.2)开始,类加载过程采取了双亲委派模型(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是启动类加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。

 

双亲委派模型:要求除了顶层的启动类加载器外,其余加载器都应当有自己的父类加载器。类加载器之间的父子关系,一般不会以继承的关系来实现,而是通过组合关系复用父加载器的代码。

  工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。

每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,

只有到父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

  为什么要使用:Java类随着它的类加载器一起具备了一种带优先级的层次关系。

比如java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,

因此Object类在程序的各个类加载器环境中,都是同一个类。

自己编写一个与rt.jar类库中已有类重名的java类,可以正常编译,但无法被加载运行。

 

委托机制的意义 — 防止内存中出现多份同样的字节码 
比如两个类A和类B都要加载System类:

如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。

 

能不能自己写个类叫java.lang.System?

答案:通常不可以,但可以采取另类方法达到这个需求。 
解释:为了不让我们写System类,类加载采用委托机制,这样可以保证父类加载器优先,父类加载器能找到的类,子加载器就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。

但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。

 

 

 

1、JVM 简介

  JVM 是我们Javaer 的最基本功底了,刚开始学Java 的时候,一般都是从“Hello World ”开始的,然后会写个复杂点class ,然后再找一些开源框架,比如Spring ,Hibernate 等等,再然后就开发企业级的应用,比如网站、企业内部应用、实时交易系统等等,直到某一天突然发现做的系统咋就这么慢呢,而且时不时还来个内存溢出什么的,今天是交易系统报了StackOverflowError ,明天是网站系统报了个OutOfMemoryError ,这种错误又很难重现,只有分析Javacore 和dump 文件,运气好点还能分析出个结果,运行遭的点,就直接去庙里烧香吧!每天接客户的电话都是战战兢兢的,生怕再出什么幺蛾子了。我想Java 做的久一点的都有这样的经历,那这些问题的最终根结是在哪呢?—— JVM 。

  JVM 全称是Java Virtual Machine ,Java 虚拟机,也就是在计算机上再虚拟一个计算机,这和我们使用 VMWare不一样,那个虚拟的东西你是可以看到的,这个JVM 你是看不到的,它存在内存中。我们知道计算机的基本构成是:运算器、控制器、存储器、输入和输出设备,那这个JVM 也是有这成套的元素,运算器是当然是交给硬件CPU 还处理了,只是为了适应“一次编译,随处运行”的情况,需要做一个翻译动作,于是就用了JVM 自己的命令集,这与汇编的命令集有点类似,每一种汇编命令集针对一个系列的CPU ,比如8086 系列的汇编也是可以用在8088 上的,但是就不能跑在8051 上,而JVM 的命令集则是可以到处运行的,因为JVM 做了翻译,根据不同的CPU ,翻译成不同的机器语言。

  JVM 中我们最需要深入理解的就是它的存储部分,存储?硬盘?NO ,NO , JVM 是一个内存中的虚拟机,那它的存储就是内存了,我们写的所有类、常量、变量、方法都在内存中,这决定着我们程序运行的是否健壮、是否高效,接下来的部分就是重点介绍之。

回到顶部

2、JVM 的组成部分

我们先把JVM 这个虚拟机画出来,如下图所示:

 

从这个图中可以看到,JVM 是运行在操作系统之上的,它与硬件没有直接的交互。我们再来看下JVM 有哪些组成部分,如下图所示:

该图参考了网上广为流传的JVM 构成图,大家看这个图,整个JVM 分为四部分:

## Class Loader 类加载器 

类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java 程序,然后通过javac 编译成class 文件,那怎么才能加载到内存中被执行呢?Class Loader 承担的就是这个责任,那不可能随便建立一个.class 文件就能被加载的,Class Loader 加载的class 文件是有格式要求,在《JVM Specification 》中式这样定义Class 文件的结构:

ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];u2 fields_count;field_info fields[fields_count];u2 methods_count;method_info methods[methods_count];u2 attributes_count;attribute_info attributes[attributes_count];}

 

需要详细了解的话,可以仔细阅读《JVM Specification 》的第四章“The class File Format ”,这里不再详细说明。

友情提示:Class Loader 只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine 负责的。

## Execution Engine 执行引擎 

执行引擎也叫做解释器(Interpreter) ,负责解释命令,提交操作系统执行。

## Native Interface 本地接口

本地接口的作用是融合不同的编程语言为Java 所用,它的初衷是融合C/C++ 程序,Java 诞生的时候是C/C++ 横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++ 程序,于是就在内存中专门开辟了一块区域处理标记为native 的代码,它的具体做法是Native Method Stack 中登记native 方法,在Execution Engine 执行时加载native libraies 。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java 程序驱动打印机,或者Java 系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket 通信,也可以使用Web Service 等等,不多做介绍。

## Runtime data area 运行数据区 

运行数据区是整个JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java 生态系统如此的繁荣,得益于该区域的优良自治。

 

整个JVM 框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!

回到顶部

3、JVM加载class文件的原理机制 

   Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。

  类装载方式,有两种 
      1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
      2.显式装载, 通过class.forname()等方法,显式加载需要的类 
    隐式加载与显式加载的区别:两者本质是一样? 

     Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。

   Java的类加载器有三个,对应Java的三种类:(java中的类大致分为三种:   1.系统类   2.扩展类 3.由程序员自定义的类 )

     Bootstrap Loader  // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
            | 
          - - ExtClassLoader   // 负责加载扩展类(就是继承类和实现类)
                          | 
                      - - AppClassLoader   // 负责加载应用类(程序员自定义的类)

 三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。

委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。

      我们可以通过这样的代码来获取类加载器:

ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();

 

注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。
      

前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:

1.装载:查找和导入class文件;

2.连接:

      (1)检查:检查载入的class文件数据的正确性;

      (2)准备:为类的静态变量分配存储空间;

      (3)解析:将符号引用转换成直接引用(这一步是可选的)

3.初始化:初始化静态变量,静态代码块。

      这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。

-------------我是低调的分割线--------------------------

 

 附录:

https://www.cnblogs.com/Qian123/p/5707562.html

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/williamjie/p/11167920.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/350054.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xtend怎么使用_使用Xtend构建Vaadin UI

xtend怎么使用今天,我决定向Xtend打个招呼。 我希望学习一些新的编程语言。 选择一个标准的清单并不多。 它必须是在JVM上运行的编程语言, 如果我不需要学习用于建筑应用的全新生态系统,那就太好了。 我检查了几个选项。 JVM的编程语言列表…

linux mysql 写shell_Linux—编写shell脚本操作数据库执行sql

修改数据库数据在升级应用时,我们常常会遇到升级数据库的问题,这就涉及到sql脚本的编写。一般我们会通过写sql脚本,然后将xxx.sql脚本放到数据库中进行source xxx.sql执行。本篇文章,我们可以通过写shell脚本来执行数据库操作。配…

DMN 1.1 XML:使用Drools 7.0从建模到自动化

我是一名自由顾问,但今天我是一名博士生。 本文的全球背景是企业架构(EA),它需要对企业进行建模。 由于EA的一个方面是业务流程建模,所以我使用BPMN已有数年之久,但是这种表示方式并不十分适合表示决策标准…

mysql安装模块解释_MySQL的模块不能安装的解决方法_MySQL

我们最常用的 DBD::mysql 模块,我发现是难住很多人的地方.因为安装老是失败,下面我介绍一下解决方法,比如我使用 cpanm 安装,有时就出 /home/nue2501j/work/DBD-mysql-4.010/blib/arch/auto/DBD/mysql/mysql.so: undefined symbol: DBIc_TRACE_LEVEL at /usr/lib/perl5/5.8.5/i…

nginx访问目录是没加/的重定向控制

static 模块提供了root与alias功能;发现目标是目录时;但URI末尾未加/时;会返回301重定向;重定向后会加/ 指令 Syntax: server_name_in_redirect on | off;# 控制返回那个域名 Default: server_name_in_redirect off; Context: ht…

oracle mysql 字段_Oracle和MySQL修改字段

一、修改字段名:Oracle:ALTER TABLE tableName RENAME COLUMN oldColumnName TO newColumnName;eg: ALTER TABLE web RENAME COLUMN wen_name TO web_name;MySQL:ALTER TABLE tableName CHANGE oldColumnName newColumnName newColumnDataTyp…

Kafka REST Proxy for MapR Streams入门

介绍 MapR生态系统软件包2.0(MEP)随附了一些与MapR流有关的新功能: MapR Streams的Kafka REST代理为MapR Streams和Kafka集群提供RESTful接口,以使用和生成消息并执行管理操作。 Kafka Connect for MapR Streams是一个实用程序&…

开张大吉

在Windows的天地中做了五年开发之后,又跑到了开源的Linux/Java世界遨游了五年,于最近半年,又重新回到M$阵营。这半年来,迫于项目进度的压力,较少与他人交流,项目中虽然采用了VS2003,但是运用的思…

mysql四种输入_mysql四种事务隔离级别

mysql事务并发问题ACID什么的就不啰嗦了。mysql多个事务并发的时候,可能会出现如下问题:1. 更新丢失即两个事务同时更新某一条数据,后执行的更新操作会覆盖先执行的更新操作,导致先执行的更新结果丢失。2. 脏读即一个事务会读到另…

apache hadoop_春天遇见Apache Hadoop

apache hadoopSpringSource 刚刚宣布了适用于Apache Hadoop的Spring的第一个GA版本 。 该项目的目的是简化基于Hadoop的应用程序的开发。 您可以下载该项目在这里 ,并检查了Maven的文物在这里 。 Apache Hadoop的Spring诞生是为了解决Hadoop应用程序构建不良的问题…

sinacloud webpy mysql_Mysqldb和webpy的安装

1.首先安装mysqlsudo apt-get install mysql-server2.然后安装libmysqld-dev和libmysqlclient-dev,否则在安装Mysqldb的时候会报找不到mysql_config文件sudo apt-get install libmysqld-devsudo apt-get install libmysqlclient-dev修改site.cfg中的mysql_config的配…

消费者驱动的Pact和Spring Boot测试

最近,我的一位同事偶然发现了Pact.io ,我们的当前应用程序已发展到50多种服务,并且我们开始出现一些集成测试失败和脆弱的开发/验收测试环境。 因此,我们决定研究尝试与此相关的方法。 我从阅读开始: https : //docs.…

python空格怎么加密_使用Python的RSA加密

如果您想使用python高效地编码RSA加密,我的github存储库肯定会理解和解释python中RSA的数学定义RSA密钥生成def keyGen(): Generate Keypair i_prandint(0,20)i_qrandint(0,20)# Instead of Asking the user for the prime Number which in case is not feasible,# …

MySQL中字符串函数详细介绍

MySQL字符串函数对于针对字符串位置的操作,第一个位置被标记为1。 ASCII(str)返回字符串str的 最左面字符的ASCII代码值。如果str是空字符串, 返回0。如果str是NULL,返回NULL。 mysql> select ASCII(2);-> 50mysql> select ASCII(2)…

java 转储快照分析_分析Java核心转储

java 转储快照分析在本文中,我将向您展示如何调试Java核心文件,以查看导致JVM崩溃的原因。 我将使用在上一篇文章: 生成Java Core Dump中生成的核心文件。 您可以通过以下几种方法来诊断JVM崩溃: hs_err_pid日志文件 当JVM中发生…

zbox mysql_20190213云服务器部署禅道

1.系统环境:腾讯云服务器;Centos 7.02.工具:禅道的压缩包(需要是tar.gz文件名的)、Xshell、Xftp;3.安全组规则的设置;4.端口号的设置以下为详细步骤:需要在空白的服务器上去进行操作。还需要再琢磨的。1、x…

Java编程语言的历史和未来

通过AppDynamics解决应用程序问题的速度提高了10倍–以最小的开销在代码级深度监视生产应用程序。 开始免费试用! 作为Internet上著名的编程语言 ,Java对人们如何浏览数字世界产生了深远的影响。 Java功能设置了用户对他们访问互联网的设备的性能期望的…

fmdb和mysql的区别_FMDB

什么是数据库数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,我们可以很方便的对数据库中的数据进行增、删、改、查操作数据库类型数据库可以分为2大种类关系型数据库(主流)关系型数据库(主流)对象型数据库常用关系型数据库PC端:Oracle、MySQL、SQL S…

RabbiqMQ快速入门

RabbitMQ 官网地址: https://www.rabbitmq.com/ 一个遵循AMQP协议,开源面向消息的中间件,支持多种编程语言。 Rabbitmq 能做什么? 逻辑解耦,异步的消息任务消息持久化,重启不影响削峰,大规模的消息处理主要的特点 可靠性&#xf…

Java命令行界面(第13部分):JArgs

JArgs 1.0的区别在于,这是我的第13篇文章的主题,该文章是关于Java命令行参数解析的。 JArgs是一个开放源代码( BSD许可证 )库,主要由Steve Purcell和Ewan Mellor等 不同的贡献者支持。 事实证明,这在第一次…