JVM-ClassLoader(转)

在加载阶段主要用到的是方法区:

方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。

如果把方法的代码看作它的“静态”部分,而把一次方法调用需要记录的临时数据看做它的“动态”部分,那么每个方法的代码是只有一份的,存储于JVM的方法区中;每次某方法被调用,则在该调用所在的线程的的Java栈上新分配一个栈帧,用于存放临时数据,在方法返回时栈帧自动撤销。

在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成验证、准备、解析(可选的)。
    装载:查找和导入类或接口的二进制数据; 
    链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 
          验证:检查导入类或接口的二进制数据的正确性; 
          准备:给类分配所需存储空间; 
          解析:将常量池中符号引用转成直接引用,根据实际需要,可以推迟到初始化之后进行
     初始化:也就是为变量赋予正确的初始值,如激活类的静态变量,初始化Java代码和静态Java代码块

2.1 装载

装载阶段主要是将java字节码以二进制的方式读入到jvm内存中,然后将二进制数据流按照字节码规范解析成jvm内部的运行时数据结构。java只对字节码进行了规范,并没有对内部运行时数据结构进行规定,不同的jvm实现可以采用不同的数据结构,这些运行时数据结构是保存在jvm的方法区中(hotspot jvm的内部数据结构定义可以参见撒迦的博文借助HotSpot SA来一窥PermGen上的对象)。当一个类的二进制解析完毕后,jvm最终会在堆上生成一个java.lang.Class类型的实例对象,通过这个对象可以访问到该类在方法区的内容。

jvm规范并没有规定从二进制字节码数据应该如何产生,事实上,jvm为了支持二进制字节码数据来源的可扩展性,它提供了一个回调接口将通过一个类的全限定名来获取描述此类的二进制字节码的动作开放到jvm的外部实现,这就是我们后面要讲到的类加载器,如果有需要,我们完全可以自定义一些类加载器,达到一些特殊应用场景。由于有了jvm的支持,二进制流的产生的方式可以是:

(1) 从本地文件系统中读取

(2) 从网络上加载(典型应用:java Applet)

(3) 从jar,zip,war等压缩文件中加载

(4) 通过动态将java源文件动态编译产生(jsp的动态编译)

(5) 通过程序直接生成。

2.2 连接

连接阶段主要是做一些加载完成之后的验证工作,和初始化之前的准备一些工作,它细分为三个阶段。

2.2.1 验证

验证是连接阶段的第一步,它主要是用于保证加载的字节码符合java语言的规范,并且不会给虚拟机带来危害。比如验证这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。按照验证的内容不同又可以细分为4个阶段:文件格式验证(这一步会与装载阶段交叉进行),元数据验证,字节码验证,符号引用验证(这个阶段的验证往往会与解析阶段交叉进行)。

2.2.2 准备

准备阶段主要是为类的静态变量分配内存,并设置jvm默认的初始值。对于非静态的变量,则不会为它们分配内存。

在jvm中各类型的初始值如下:

int,byte,char,long,float,double 默认初始值为0

boolean 为false(在jvm内部用int表示boolean,因此初始值为0)

reference类型为null

对于final static基本类型或者String类型,则直接采用常量值(这实际上是在编译阶段就已经处理好了)。

2.2.3 解析

解析过程就是查找类的常量池中的类,字段,方法,接口的符号引用,将他们替换成直接引用的过程。

a.解析过程主要针对于常量池中的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info四种常量。

b. jvm规范并没有规定解析阶段发生的时间,只是规定了在执行anewarray,checkcast,getfield,getstatic,instanceof,invokeinterface,invokespecial,invokespecial,invokestatic,invokevirtual,multinewaary,new,putfield,putstatic这13个指令应用于符号指令时,先对它们进行解析,获取它们的直接引用.

c. jvm对于每个加载的类都会有在内部创建一个运行时常量池(参考上面图示),在解析之前是以字符串的方式将符号引用保存在运行时常量池中,在程序运行过程中当需要使用某个符号引用时,就会促发解析的过程,解析过程就是通过符号引用查找对应的类实体,然后用直接引用替换符号引用。由于符号引用已经被替换成直接引用,因此后面再次访问时,无需再次解析,直接返回直接引用。

2.3 初始化

初始化阶段是根据用户程序中的初始化语句为类的静态变量赋予正确的初始值。这里初始化执行逻辑最终会体现在类构造器方法<clinit>()方中。该方法由编译器在编译阶段生成,它封装了两部分内容:静态变量的初始化语句和静态语句块。

2.3.1 初始化执行时机

jvm规范明确规定了初始化执行条件,只要满足以下四个条件之一,就会执行初始化工作

(1) 通过new关键字实例化对象读取设置类的静态变量调用类的静态方法(对应new,getstatic,putstatic,invokespecial这四条字节码指令)。

(2) 通过反射方式执行以上行为时。

(3) 初始化子类的时候,会触发父类的初始化。

(4) 作为程序入口直接运行时的主类。

2.3.2 初始化过程

初始化过程包括两步:

(1) 如果类存在直接父类,并且父类没有被初始化则对直接父类进行初始化。

(2) 如果类当前存在<clinit>()方法,则执行<clinit>()方法。

需要注意的是接口(interface)的初始化并不要求先初始化它的父接口。(接口不能有static块)

2.3.3 <clinit>()方法存在的条件

并不是每个类都有<clinit>()方法,如下情况下不会有<clinit>()方法:

a. 类没有静态变量也没有静态语句块

b.类中虽然定义了静态变量,但是没有给出明确的初始化语句。

c.如果类中仅包含了final static 的静态变量的初始化语句,而且初始化语句采用编译时常量表达时,也不会有<clinit>()方法。

2.3.4 并发性

在同一个类加载器域下,每个类只会被初始化一次,当多个线程都需要初始化同一个类,这时只允许一个线程执行初始化工作,其他线程则等待。当初始化执行完后,该线程会通知其他等待的线程。

2.4 在使用过程中类,对象在方法区和堆上的分布状态

public class TestThread extends Thread implements Cloneable {

    public static void main(String[] args) {
        TestThread t = new TestThread();
        t.start();
    }
}

上面这代码中TestThread及相关类在jvm运行的存储和引用情况如下图所示:

t在main线程VM Stack的局部变量表中

其中 t 作为TestThread对象的一个引用存储在线程的栈帧空间中,Thread对象及类型数据对应的Class对象实例都存储在堆上,类型数据存储在方法区,前面讲到了,TestThread的类型数据中的符号引用在解析过程中会被替换成直接引用,因此TestThread类型数据中会直接引用到它的父类Thread及它实现的接口Cloneable的类型数据。

在同一个类加载器空间中,对于全限定名相同的类,只会存在唯一的一份类的实例及类型数据。实际上类的实例数据和其对应的Class对象是相互引用的。

http://my.oschina.net/tryUcatchUfinallyU/blog/144133

时间: 2024-10-02 01:32:52

JVM-ClassLoader(转)的相关文章

jvm classLoader architecture :

a.Bootstrap ClassLoader/启动类加载器 主要负责jdk_home/lib目录下的核心         api 或 -Xbootclasspath 选项指定的jar包装入工作. B.Extension ClassLoader/扩展类加载器 主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作 C.System ClassLoader/系统类加载器 主要负责java -classpath/-Djava.clas

JVM - classLoader

此文参考:  http://www.cnblogs.com/liu-5525/p/5614425.html 1. classLoader 如何加载 class ClassLoader 负责将 .class 文件(可能在disk上,可能在网络上) 加载到 RAM 里面, 并为之生成对应的 [java.lang.Class] object. 当 JVM 启动的时候 会形成由三个 ClassLoader 组成的 初始类加载器 层次结构: bootstrap classloader --> extens

JVM ClassLoader加载过程

1)三个类加载器: bootstrap classloader - 引导(也称为原始)类加载器,它负责加载Java的核心类. extension classloader - 扩展类加载器,它负责加载JRE的扩展目录中JAR的类包. system classloader - 系统(也称为应用)类加载器,加载应用程序的类. bootstrap classloader不是一个真正的ClassLoader实例 2)获取引导类加载器加载了哪些类: URL[] urls=sun.misc.Launcher.

深入浅出 JVM ClassLoader

# 前言 在 JVM 综述里面,我们说,JVM 做了三件事情,Java 程序的内存管理, Java Class 二进制字节流的加载(ClassLoader),Java 程序的执行(执行引擎).我们也说,我们大部分情况下只关注前2个.在前面的文章中,我们已经分析了内存关系相关的,包括运行时数据区,GC 相关.今天我们要讲的就是类加载器. 在 JVM 综述 里,我们已经大致分析了一些概念.而今天的文章将详细的阐述类加载器. 首先,我们要了解类加载器,当然,了解的目的是为了更好的开发,通过对类加载器的

别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.ClassLoader源码分析 11.自定义类加载器 12.加载类的三种方式 13.总结 14.特别注意 @ 前言 你是否真的理解java的类加载机制?点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了java的类加载机制,若结果出乎你的意料,那就很有必要来了解了解j

java classLoader体系结构使用详解

原创整理不易,转载请注明出处:java classLoader体系结构使用详解 代码下载地址:http://www.zuidaima.com/share/1774052029516800.htm jvm classLoader architecture: Bootstrap ClassLoader/启动类加载器 主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作. Extension ClassLoader/扩展类加载器  主要负

深入理解ClassLoader(五)—类的卸载

原文地址:http://yhjhappy234.blog.163.com/blog/static/316328322011101413730764/?suggestedreading&wumii 我们知道,当一个类被加载.连接和初始化之后,他的生命周期就开始了,当该类的class对象不再被引用之后,该类的生命周期也就结束了,之后,该类会被类加载器卸载! 我们来看以下代码: package com.yhj.jvm.classloader.uninstall;< xmlnamespace pre

自定义 ClassLoader 实现动态加载

不同的classloader加载的相同的类,会被jvm认为是不同的类 要想实现热加载,几个原则是要记住的: 每次实例化新的classloader 动态加载类文件,比如rul或者文件等等 记载的类使用反射进行方法调用,或者上溯为接口进行调用. 下面看一个例子: 首先定义一个被调用的简单类AppObject: package com.dataguru.jvm.classloader; public class AppObject { public void sayHello(){ System.ou

[Tomcat] Tomcat的classloader

Tomcat Classloader JVM Classloader 首先,你需要了解一下JVM的Classloader机制(详细请自行google之).简而言之,JVM的classloader加载继承关系分为BootstarpClassLoader --> ExtClassLoader --> SystemClassLoader,应用的WebAppClassLoader继承自SystemClassLoader,在加载具体某个类时,一般会先委托给父类ClassLoader,当父类ClassLo

JVM 数组创建的本质

1.创建数组 创建一个MyParent4[] 数组 public class MyTest4 { public static void main(String[] args) { MyParent4[] myParent4s = new MyParent4[1]; System.out.println(myParent4s.getClass()); } } class MyParent4{ static { System.out.println("MyParent4 static block&q