本文来自:曹胜欢博客专栏。转载请注明出处:http://blog.csdn.net/csh624366188

类加载器,顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。其实我们研究类加载器主要研究的就是类的生命周期

首先来了解一下jvm(java虚拟机)中的几个比较重要的内存区域,这几个区域在java类的生命周期中扮演着比较重要的角色:

方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。

常量池:常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。

堆区:用于存放类的对象实例。

栈区:也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。

类的生命周期
        当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,这里我们主要来研究类加载器所执行的部分,也就是加载,链接和初始化。如图所示:
                  

下面我先简单看一下类加载器所执行的三部分的简单介绍
1、加载:查找并加载类的二进制数据

2、连接

–验证:确保被加载的类的正确性

–准备:为类的静态变量分配内存,并将其初始化为默认值

–解析:把类中的符号引用转换为直接引用

3、初始化:为类的静态变量赋予正确的初始值

从上边我们可以看出类的静态变量赋了两回值。这是为什么呢?原因是,在连接过程中时为静态变量赋值为默认值,也就是说,只要是你定义了静态变量,不管你开始给没给它设置,我系统都为他初始化一个默认值。到了初始化过程,系统就检查是否用户定义静态变量时有没有给设置初始化值,如果有就把静态变量设置为用户自己设置的初始化值,如果没有还是让静态变量为初始化值

类的加载、连接和初始化

类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 。这里的class对象其实就像一面镜子一样,外面是类的源程序,里面是class对象,它实时的反应了类的数据结构和信息。

加载.class文件的方式 

1、从本地系统中直接加载

2、通过网络下载.class文件

3、从zip,jar等归档文件中加载.class文件

4、从专有数据库中提取.class文件

5、将Java源文件动态编译为.class文件

类的加载过程

结论:

1、类的加载的最终产品是位于堆区中的Class对象

2、Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口

Java虚拟机给我们提供了两种类加载器:

1、Java虚拟机自带的加载器

1)根类加载器(使用C++编写,程序员无法在Java代码中获得该类)

2)扩展加载器,使用Java代码实现

3)系统加载器(应用加载器),使用Java代码实现

2、用户自定义的类加载器

java.lang.ClassLoader的子类

用户可以定制类的加载方式

我们看一下API对ClassLoader的介绍:

类加载器是负责加载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。每个class对象都包含一个对定义它的 ClassLoader 的引用。

我们再来看一下Class类的一个方法getClassLoader

public ClassLoader getClassLoader()

返回该类的类加载器。有些实现可能使用 null 来表示根类加载器。如果该类由根类加载器加载,则此方法在这类实现中将返回 null。

下面我们来看一个小例子来验证一下:

  1. package com.bzu.csh;
  2. public class Test
  3. {
  4. public static void main(String[] args) throws Exception
  5. {
  6. Class clazz = Class.forName("java.lang.String");
  7. System.out.println(clazz.getClassLoader());
  8. Class clazz2 = Class.forName("com.bzu.csh.ABC");
  9. System.out.println(clazz2.getClassLoader());
  10. }
  11. }
  12. class ABC
  13. {
  14. }

看一下打印结果,一目了然:

从上面打印结果可以看出,第一个为null,也就是它用根类加载器加载的,第二个是我们自己写的类,也就是说,我们自己写的那个类用sun.misc.Launcher$AppClassLoader@1372a1a加载器加载的,我们可以看到APP,也就是应用类加载器,也就是系统加载器

还记得我们以前用过的动态代理吧,InvocationHandler,当我们利用proxy对象调用newProxyInstance建立一个代理类时,我们要给他传一个ClassLoader,也就是类加载器,如下:

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)
  4. throws IllegalArgumentException

当时我们学习的时候,只知道这里的loader随便给他设置一个类的类加载器就可以。现在我们来想想为什么这里需要一个类加载器呢?我们知道这个newProxyInstance是动态的给我们生成一个代理类,然后根据这个代理类生成一个代理对象。动态生成这个代理类之后我们不得把他加载到内存里吗,加载到内存里我们才可以用他。用什么加载到内存里,只有类加载器,所以我们要给他指定一个类加载器。

类加载器并不需要等到某个类被“首次主动使用”时再加载它 。JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误) 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误 。大家在做web开发的时候有可能会出现这种问题,比如我们在做测试的时候是用的jdk1.6,而我们在部署的时候我们用的是jdk1.5.这时候就很可能汇报LinkageError错误,版本不兼容。

类的连接

类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。

验证:当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。很多人都感觉,既然这个类都通过编译加载到内存里了,那肯定就是合法的了,为什么还要验证呢,这是因为这里的验证时为了避免有人恶意编写class文件,也就是说并不是通过编译得到的class文件。所以这里验证其实是检查的class文件的内部结构是否符合字节码的要求

准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:
基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
引用类型的默认值为null。
常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。

解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。那么什么是符号引用,什么又是直接引用呢?我们来举个例子:我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:比如山东省滨州市滨城区18号张三,通过这个信息我们就能找到这个人了。这里,123456790就好比是一个符号引用,而山东省滨州市滨城区18号张三就是直接引用。在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。

[Java]类的生命周期(上)类的加载和连接[转]的更多相关文章

  1. java 静态变量生命周期(类生命周期)

    Static: 加载:java虚拟机在加载类的过程中为静态变量分配内存. 类变量:static变量在内存中只有一个,存放在方法区,属于类变量,被所有实例所共享 销毁:类被卸载时,静态变量被销毁,并释放 ...

  2. java 静态变量生命周期(类生命周期)(转)

    Static: 加载:java虚拟机在加载类的过程中为静态变量分配内存. 类变量:static变量在内存中只有一个,存放在方法区,属于类变量,被所有实例所共享 销毁:类被卸载时,静态变量被销毁,并释放 ...

  3. 乐字节Java反射之三:方法、数组、类加载器和类的生命周期

    本文承接上一篇:乐字节Java发射之二:实例化对象.接口与父类.修饰符和属性 继续讲述Java反射之三:方法.数组.类加载器 一.方法 获取所有方法(包括父类或接口),使用Method即可. publ ...

  4. Java类的生命周期浅析

    类的生命周期?对象的生命周期?Spring bean 的生命周期?很多同学可能在学习java基础知识之初,很容易把这几个搞混.本文先来说说Java类的生命周期. 目录 知识前提 类的生命周期 加载(L ...

  5. jvm学习笔记:一、类的加载、连接、初始化

    在JAVA代码中,类型的加载.连接与初始化过程都是程序运行期间完成的. 类型的加载:将已经存在的class从硬盘加载到内存. 类型的连接:将类与类之间的关系确定好. 类型的初始化:类型 静态的变量进行 ...

  6. 深入探索Glide图片加载框架:做了哪些优化?如何管理生命周期?怎么做大图加载?

    前言 Glide可以说是最常用的图片加载框架了,Glide链式调用使用方便,性能上也可以满足大多数场景的使用,Glide源码与原理也是面试中的常客. 但是Glide的源码内容比较多,想要学习它的源码往 ...

  7. Java类的生命周期详解

    引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大多只是告 ...

  8. 【转】Java 类的生命周期详解

    一. 引 言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前国内java方面的教材大 ...

  9. 【转载】详解java类的生命周期

    原文地址:http://blog.csdn.net/zhengzhb/article/details/7517213 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑, ...

随机推荐

  1. Git与GitHub学习笔记(三).gitignore文件忽略和删除本地以及远程文件

    一.Git提供了文件忽略功能.当对工作区某个目录或者某些文件设置了忽略后,git将不会对它们进行追踪 HELP:如何在IntelliJ IDEA中使用.ignore插件忽略不必要提交的文件 问题:最近 ...

  2. js调试系列: 断点与动态调试[基础篇]

    js调试系列目录: - 额,我说的不是张敬轩的 断点 这首歌,是调试用到的断点,进入正题吧. 昨天留的课后练习 1. 分析 votePost 函数是如何实现 推荐 的.其实我们已经看到了源码,只要读下 ...

  3. Repeater控件的分页实现

    本文讲解Repeater控件与PagedDataSource相结合实现其分页功能.PagedDataSource 类封装那些允许数据源控件(如 DataGrid.GridView)执行分页操作的属性. ...

  4. 2016-2017-2 20155309 南皓芯java第六周学习总结

    教材内容详解 这一次主要学习的是第十章与第十一章的内容.主要讲述了串流,字符处理和线程以及并行API. 输入输出 串流:Java中的数据有来源(source)和目的地(destination),衔接两 ...

  5. [BZOJ 2299][HAOI 2011]向量 题解(裴蜀定理)

    [BZOJ 2299][HAOI 2011]向量 Description 给你一对数a,b,你可以任意使用(a,b), (a,-b), (-a,b), (-a,-b), (b,a), (b,-a), ...

  6. python3光学字符识别模块tesserocr与pytesseract

    OCR,即Optical Character Recognition,光学字符识别,是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程,对应图形验证码来说,它们都是一些不规则的字符,这些字符是由 ...

  7. 012_iTerm2 快捷键大全

    标签 新建标签:command + t 关闭标签:command + w 切换标签:command + 数字 command + 左右方向键 切换全屏:command + enter 查找:comma ...

  8. tar命令的实用详解(C参数和排除文件 --exclude)

    一.tar:从压缩包中解压出指定文件 [root@d176 test]# tar ztf nrpe-2.12.tar.gz |grep srcnrpe-2.12/src/nrpe-2.12/src/. ...

  9. 002_CentOS-6.4-x86_64安装包的说明

    http://mirrors.sohu.com/centos/6.6/isos/x86_64/?qq-pf-to=pcqq.group //souhu镜像下载地址 0_README.txt 25-Oc ...

  10. Linux 管道

    管道命令 " | ",竖线符号代表的就是管道符 管道是一种两个进程间进行单向通信的机制.因为管道传递数据的单向性,所以又称为半双工管道. 介绍: 管道可以根据一组命令按照数据流向的 ...