JVM构成

JVM由以下几个组成部分:

一. java程序,首先经过编译后变成.class 文件,然后交由类加载器执行,类的信息就会被加载到JVM运行时区域,将各个类信息划分到各个组件中去,最后转换成系统语言,交由执行引擎去执行。大概流程是这样,下面,开始具体的各个模块解释

 

1.ClassLoader: 类加载器,就是将class文件加载到内存,加载的过程中,会经过校验、转换解析、初始化,最终成为JVM能直接使用的类型文件。

在api文档中,ClassLoader是个抽象类,官方给的解释就是,根据一个指定的类全限定名,找到对应的class字节码文件,然后加载它使成为java.lang.class 的一个实例。

ClassLoader的生命周期过程:

加载-》验证-》准备-》解析-》初始化-》使用-》-》卸载

类加载的顺序,肯定是按照上面的步骤一步步的执行(仅仅指的是开始,而不是执行或者结束,因为这些阶段通常都是相互交叉混合进行的)。

另外,类的实例化与类的初始化是两个完全不同的概念

1. 类的实例化是指创建一个类的实例(对象)的过程

2. 类的初始化是指为类中各个成员(被static修饰的成员变量)赋初始值的过程,是生命周期中的一个阶段。

 

ClassLoader类结构分析

public abstact class ClassLoader{

        private final ClassLoader parent;

       //这个方法,protectionDomain(指定保护区域),把ByteBuffer的内容转换成java类,这个方法被声明为final的

        defineClass(String name,java.nio.ByteBuffer b,ProtectionDomain protectionDomain)

     

      //这个方法,把字节数组b中的内容转换成了java类,其开始偏移为off,这个方法被声明为final的

      defineClass(String name,byte[] b,int off,int len);

 

       findClass(String name) // 查找指定名称的类

       loadClass(String name)// 加载指定的名称的类

       resolveClass(Class<?>) // 链接指定的类

      }

其中,defineClass方法用来将字节流解析成JVM能够识别的class对象,有了这个方法意味着我们不仅仅可可以通过class文件实例化对象,还可以通过其他方式实例化对象,如果我们通过网络收到一个类的字节码,拿到这个字节码流直接创建类的Class对象形式实例化对象。如果直接调用这个方法生成类的Class对象,这个类的Class对象还没有resolve,这个resolve将会在这个真正实例化时才进行。

defineClass通常是和findClass方法一起使用的,我们通过覆盖ClassLoader父类的findClass方法来实现类的加载规则,从而取得要加载的字节码,然后调用defineClass方法生成类的Class对象,如果你想在类加载JVM中时就被链接,那么可以接着调用另一个resolveClass方法,当然你也可以选择让JVM来解决什么时候才链接这个类。

 

ClassLoader的等级加载机制

Java默认提供的三个ClassLoader

1.BootStrap ClassLoader

称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK的核心类库,如:rt.jar,resources.jar,charsets.jar等,这个ClassLoader完全是JVM字节控制的,别人也访问不到这个类,所以BootStrapClassLoader不遵循委托机制,没有子加载器

2.ExtClassLoader

 称为扩展类加载器,负责加载Java的扩展类库,Java虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载Java类。默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar

3. AppClassLoader

 称为系统加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java应用的类都是由他来完成加载的。

 

除了引导类加载器( BootStrap ClassLoader)之外,所有类加载器都有一个父类加载器,对于系统提供的类加载器来说,系统类加载器(如: AppClassLoader)的父类加载器是扩展类加载器(ExtClassLoader),而扩展类加载器的父类加载器是引导类加载器

 

ClassLoader加载的原理

ClassLoader使用的是双亲委托模式来搜索加载类的

ClassLoader使用的是双亲委托模式来搜索加载类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是一个组合的关系),虚拟机内置的类加载器(BootStrapClassLoader)本身没有父类加载器,但可以用作其他ClassLoader实例的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下一次检查的,首先由最顶层的类加载器BootStrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader视图加载,如果也没加载到,继续交由AppClassLoader进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或者网络等URL中加载该类。如果他们都没有加载到这个类,则抛出ClassNotFoundException。否则将这个找到的类生成一个类的定义,并把它加载到内存当中,最后返回这个类在内存中的Class实例对象。

 

双亲委托模型的好处:避免重复加载,当父加载器已经加载了该类的时候,就没必要ClassLoader再加载一次。

但是有个问题,顶层的ClassLoader无法加载底层的ClassLoader的类

比如:javax.xml.parsers包中定义了xml解析的类接口 Service Provider Interface SPI 位于rt.jar 即接口在启动ClassLoader中

而SPI的实现类,在AppLoader。这样就无法用BootStrapClassLoader去加载SPI的实现类

解决:JDK提供了一个方法:Thread.setContextClassLoader(),用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题

基本思想就是,在顶层ClassLoader中,传入底层ClassLoader的实例

双亲模式的破坏:

双亲模式是默认的模式,但是不是必须这么做,Tomcat的WebappClassLoader就会先加载自己的class,找不到再委托parent;

ok,到此,类加载器,基本上介绍完了。

 

二:JVM运行时区域

根据上面的图可以看出,JVM运行时区域划分为几个部分:

1. 方法区:各个线程共享的区域,存放类信息,常量,静态变量

2. java堆 :也是线程共享的区域,我们类的实例就存放在这个区域,可以想象一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutMemoryError异常。

3. java栈:每个线程私有的的区域,它的生命周期和线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫”栈帧“,而栈帧中包括了方法中的局部变量、用于存放中间状态的操作栈,这里有很多细节,如果java栈空间不足,程序会抛出StackOverflowError异常,  递归如果深度很深,就会执行大量的犯法,方法越多java栈的空间就越大

4. 本地方法栈和java栈类似:只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法的接口,最终调用本地方法库,实现与操作系统、硬件交互的目的。

5. 程序计数器/pc寄存器:针对上面的流程,我们的类已经加载了,实例对象、方法、静态变量都去了自己该去的地方,那么问题来了 ,程序该怎么执行,哪个方法先执行,这些指令执行的顺序,就是PC寄存器在管,控制执行的顺序。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值