注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

一线天色 天宇星辰

天下武功,唯快不破

 
 
 

日志

 
 

深入剖析Classloader(一)--类的主动使用与被动使用  

2011-06-05 19:39:11|  分类: JVM |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

我们知道java运行的是这样的,首先java编译器将我们的源代码编译成为字节码,然后由JVM将字节码load到内存中,接着我们的程序就可以创建对象了,我们知道JVM将字节码load到内存之后将将建立内存模型(JVM的内存模型我们将在稍后阐述),那JVM是怎么将类load到内存中的呢?对了,是通过Classloader,今天我们就来深入探讨一下Classloader

首先我们来看一段诡异的代码(一段单实例测试代码)。

package com.yhj.jvm.classloader;

/**

 * @Description:单例初始化探究

 * @Author YHJ  create at 2011-6-4 下午08:31:19

 * @FileName com.yhj.jvm.classloader.ClassLoaderTest.java

 */

class Singleton{

   

    private static Singleton singleton=new Singleton();

    private static int counter1;

    private static int counter2 = 0;

   

    public Singleton() {

       counter1++;

       counter2++;

    }

    public static int getCounter1() {

       return counter1;

    }

    public static int getCounter2() {

       return counter2;

    }

    /**

     * @Description:实例化

     * @return

     * @author YHJ create at 2011-6-4 下午08:34:43

     */

    public static Singleton getInstance(){

       return singleton;

    }

   

}

/**

 * @Description: 测试启动类

 * @Author YHJ  create at 2011-6-4 下午08:35:13

 * @FileName com.yhj.jvm.classloader.ClassLoaderTest.java

 */

public class ClassLoaderTest {

    /**

     * @Description:启动类

     * @param args

     * @author YHJ create at 2011-6-4 下午08:30:12

     */

    @SuppressWarnings("static-access")

    public static void main(String[] args) {

       Singleton singleton=Singleton.getInstance();

       System.out.println("counter1:"+singleton.getCounter1());

       System.out.println("counter2:"+singleton.getCounter2());

 

    }

 

}

我们先猜测一下运行结果

然后我们再来调换一下单实例生成的顺序,将

    private static Singleton singleton=new Singleton();

    private static int counter1;

    private static int counter2 = 0;

修改为

    private static int counter1;

    private static int counter2 = 0;

    private static Singleton singleton=new Singleton();

再猜测一下结果,然后运行一下,看和你的猜测一致不?(是不是感觉很诡异)

好吧,我们先不看这段程序,先介绍相关的内容,等介绍完了你就明白这段诡异的代码为什么这么执行了!

我们知道我们运行刚才这段java程序是通过执行ClassLoaderTestmain函数引导起来的,而当我们执行完2个打印语句之后,JVM就停止了运行。这就是我们程序的生命周期。

在以下几种情况下JVM将结束自己的生命周期

1.         执行了System.exit()方法(具体可参见JDKAPI文档)

2.         程序正常执行结束

3.         程序在执行过程中遇到了错误或异常而异常终止

4.         由于操作系统出现错误而导致JVM进程终止

类通过JVMClassloader加载到内存经过以下几个步骤

加载 --> 连接 --> 初始化

?加载:查找并加载类的二进制数据

?连接

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

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

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

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

我来分别解释一下这三个阶段都做了什么事情

1.         加载就是将二进制的字节码通过IO输入到JVM中,我们的字节码是存在于硬盘上面的,而所用的类都必须加载到内存中才能运行起来,加载就是通过IO把字节码从硬盘迁移到内存中。

2.         连接分为3个阶段,验证,准备和解析。

1)         验证这里可能大家会疑问了,我们的类不是通过JVM编译成的字节码的吗,为什么这里还要验证加载类的正确性,难道通过Java虚拟机的javac编译器生成的字节码还会有错误不成?当然,javac编译出来的类都是正确的,但是如果是通过其他途径生成的字节码呢?是不是正确的呢?就比如你自己建一个文本文件,然后重命名该文件为Test.class,然后让JVM来运行这个类,显然是错误的。当然因为JDK的源码是开放的,所以JVM字节码的生成规则也是公开的,所以也有一些第三方的软件可以生成符合JVM规范的字节码文件,如CGlib

2)         准备:为类的静态变量分配内存,并将其初始化为默认值,这里我们一定要看清楚是为静态变量分配内存,而不是我们的实例变量,为什么我要强调静态变量,因为实例变量是什么时候产生的,是生成实例的时候产生的,而我们一般是在new一个对象的时候才对这个类进行实例化(前提是这个类已经被加载),而我们现在还没有加载完类,所以这个时候只能对静态变量分配内存空间(静态变量是属于这个类的而不属于某个对象),这个一定要分清楚。然后为该静态变量初始化为默认值(这个大家应该不陌生,int类型是0boolean就是false,引用类型是null等)。

3)         解析:把类中的符号引用转换为直接引用,这个我们等下在讨论(后面我们会讲什么是符号引用,什么是直接引用)

3.         初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默认值,这里是赋予正确的初始值,什么是正确的初始值,就是用户给赋予的值。我们来看一个例子

class Test{

private static int a = 1;

}

我们知道,这个类加载好之后,a的值就是1,但实际是这样子的,类在加载的连接阶段,将a初始化为默认值0int的默认值是0),然后在初始化阶段将a的值赋予为正确的初始值1. 我们看到最终a的值是等于1,但是实际的运行中是有一个将0赋予a的过程,这个过程放生在连接的准备阶段。类的初始化还有另外的一种形式,代码如下

class Test{

private static int a ;

static{

a=1;

}

}

这里强调一点,这个时候还是没有类的实例生成的,这点一定要注意!

《深入java虚拟机第二版》里面有一个图阐述了对应的关系,如下

深入剖析Classloader(一) - 一线天色 天宇星辰 - 一线天色 天宇星辰

 Java

程序对类的使用方式可分为2种,主动使用和被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用时才初始化他们。”

主动使用(六种)

1)   创建类的实例 (new Integer())

2)   访问某个类或接口的静态变量,或者对该静态变量赋值 (读写静态变量)

3)   调用类的静态方法

4)   反射

(如Class.forName(“com.yhj.jvm.classloader.ClassLoaderTest”)

5)   初始化一个类的子类 (初始化子类的过程中会主动使用父类的构造方法)

6)   –Java虚拟机启动时被标明为启动类的类(含有main方法并且是启动方法的类)

除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化 (除了上述6种情况以外,都不会执行初始化,只会执行加载和连接)

好了,讲到这里我们大概知道类加载的几个步骤,那我们现在来详细的介绍一下类加载这个过程中的一些细节!

类的加载:累的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区里面(具体的JVM内存模型我们会在后面讲到,这里可以参考下面JVM的内存模型图),然后在堆区创建一个java.lang.Class的对象,用于封装类在方法区内的数据结构!我们知道我们对于一个类可以创建很多个对象,但是这些对象共享同样的数据结构,而这个数据结构就是在加在过程中创建的这个class对象。我们可以通过 类名.class或者对象名.getClass()获取这个对象!无论创建了多少个实例对象,这个class的对象始终只有一个,类里面所有的结构都可以通过class对象获取,因此class对象就像一面镜子一样,可以反射一个类的内存结构,因此class是整个反射的入口!通过class对象我们可以反射的获取某个对象的数据结构,访问对应数据结构中的数据!

深入剖析Classloader(一) - 一线天色 天宇星辰 - 一线天色 天宇星辰 
JVM
内存模型

《深入java虚拟机第二版》上面一个实例描述了一个类在加载过程中的内存模型,如下

深入剖析Classloader(一) - 一线天色 天宇星辰 - 一线天色 天宇星辰

 加载

.class文件有几种方式

1.   从本地系统中直接加载 (直接加载本地硬盘上的.class文件加载)

2.   通过网络下载.class文件 (通过java.net.URLClassLoader加载网络上的某个.class文件)

3.   zipjar等归档文件中加载.class文件 (引入外部zipjar包)

4.   从专有数据库中提取.class文件 (不常用)

5.   Java源文件动态编译为.class文件 (动态代理)

 你觉得本文对你帮助吗?  

票数:53 投票时间:2011-06-05 19:44:33 到 2014-12-31 23:00:00

  •     51(96.2%)
  •     2(3.8%)
  •     0(0%)
  •     0(0%)
  •     0(0%)
查看参与情况
编辑投票|删除投票

最新投票|博友投票

      评论这张
     
    阅读(56069)| 评论(4)
    推荐 转载

    历史上的今天

    评论

    <#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    页脚

    网易公司版权所有 ©1997-2017