深入解析jvm 类加载器解析

来源:互联网 发布:c语言开发工具 win10 编辑:程序博客网 时间:2024/05/18 00:36

Java虚拟机与程序的生命周期

-执行了System..exit()方法

-程序正常执行结束

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

-由于操作系统出现错误而导致Java虚拟机进程终止

类的加载丶连接与初始化

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

连接:

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

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

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

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

Java程序对类的使用方式可分为两种

-主动使用

-被动使用

所有的Java虚拟机实现必须在每个类或接口被java程序 首次主动使用 时才初始化他们

-主动使用(6种):

 创建类的实例

 访问某个类或接口的静态变量,或者对该静态变量赋值

 调用类的静态方法

 反射

初始化一个类的子类

 java虚拟机启动时被表明为启动类的类(public static void main)

1.类的加载

类的加载是 指 将类的,class文件中的二进制数据读入到内存中,将其放在运营时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构

Java虚拟机自带的加载器

JVM 提供了三种类加载器

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

(2)拓展类加载(使用java 代码实现)Extension

(3)系统类加载器也叫应用加载器(使用java 代码实现)AppClassLoader

用户自定义的类加载器

(1)java.lang.ClassLoader的子类

(2)用户可以定制类的加载器


我们可以看Class getClassLoader()方法

某些实现可能使用null去代表根类加载器,因为BootstrapClassLoader 是不对外公开的是有C++编写的,如果这个类是使用根类加载器加载调用getClassLoader就会返回null

例子:

package com.classloader;public class Test {public static void main(String[] args) throws ClassNotFoundException{Class c1 = Class.forName("java.lang.String");System.out.println(c1.getClassLoader());Class c2 = Class.forName("com.classloader.C");System.out.println(c2.getClassLoader());}}class C{}

nullsun.misc.Launcher$AppClassLoader@73d16e93

JVM规范允许类加载器在预料某个类将要被使用时预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用那么类加载器就不会报告错误

2.类的验证

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

类的验证的内容:

-类文件的结构检查

-语义检查

-字节码验证

-二进制兼容验证

3.类的准备

 在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值,

例如 public static int a =1; 在准备阶段,将为Int类型的静态变量a分4个字节的内存空间,并且赋予默认值0。

4.类的解析丶类的初始化

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。



有如下经典代码:

  1. package test;
  2. class Singleton {     
  3. public static Singleton singleton = new Singleton();     
  4. public static int a;     
  5. public static int b = 0;     
  6. private Singleton() {     
  7. super();     
  8.         a++;     
  9.         b++;     
  10.     }     
  11. public static Singleton GetInstence() {     
  12. return singleton;     
  13.     }     
  14. }     
  15. public class MyTest {    
  16. public static void main(String[] args) {     
  17.         Singleton mysingleton = Singleton.GetInstence();     
  18.         System.out.println("a="+mysingleton.a);     
  19.         System.out.println("b="+mysingleton.b);     
  20.     }     
  21. }    

运行结果为:a=1,b=0;

如果将代码加以修改,将原来的3-5行代码如下:

public static Singleton singleton = new Singleton();     

public static int a;     

public static int b = 0; 

修改为

public static int a;     

public static int b = 0;     

public static Singleton singleton = new Singleton(); 

运行程序,结果为a=1,b=1

为什么改变了成员变量的顺序,运行结果会有所不同?为了弄清这个问题,就必须先理解Java中JVM加载类的机制。

java程序运行需要使用某个类时,如果该类还没有加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化。如下图所示:

 

 

1.类加载
当我们运行java.exe命令执行某个java程序时,由于java程序本身以.class字节码的形式存在,它不是一个可执行文件,所以需要JVM将类文件加载到内存中。
JVM中有三个类加载器:根类加载器、扩展类加载器和系统类加载器(也叫应用加载器)。根类加载器是C++实现的,扩展类加载器和应用加载器都是java语言实现的。
当运行java.exe命令执行一个java程序时,程序最基本的加载流程如下:
1.java.exe程序搜索jre目录,寻找JVM.dll,启动JVM
2.JVM运行根类加载器,加载java核心类(所有java.*开头的类)
3,根类加载器运行后,它会自动加载扩展类加载器和系统类加载器,并将扩展类加载器的父类设置为根类加载器,将应用加载器的父类设置为扩展累加载器。
4.扩展类加载器搜索jre/lib/ext目录,加载扩展API。
5.应用加载器搜索CLASSPATH目录,加载我们要运行的类。
6.类的class文件读入内存后,就会创建一个java.lang.Class对象,一旦某个类被载入JVM中,同一个类就不会再次被载入
一个类加载后,对应的Class对象,可以通过该类的实例的getClass()方法获得,Class对象有一个getClassLoader()方法,可以得到加载该类所用的类加载器。
2.连接
当类被加载后,系统就会位置创建一个对应的Class对象,接着进入连接阶段,连接又分为以下三个阶段:
1.验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致。
2.准备:负责为类的静态属性分配内存,并设置默认初始值。注意:必须是静态属性!因为此时并不存在实例对象,设置值也是默认值初始值,而不是人为给定的值
3.解析:将类的二进制数据中的符号引用替换成直接引用。
3.初始化
JVM负责对类进行初始化,也就是对静态属性进行初始化。java中对静态属性指定初始值的方式有两种:①声明静态属性时指定初始值;②使用静态初始化快为静态属性指定初始值。
需要注意的是JVM对一个类初始化时如果该类的父类没有被初始化,则会先初始化其父类,如果直接父类还有父类,那么会先初始化父类的父类,以此类推。所以,JVM最先初始化的总是java.lang.Object类,
当程序主动使用任何一个类时,系统会保证该类以及它的所有父类都会被初始化。
当java程序首次通过下面的六种方式来使用某个类或者接口时,系统就会初始化该类或者接口:
♦创建类的实例
调用某个类的静态方法
访问某个类或接口的静态属性,或者为静态属性赋值
使用反射方式强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接用java.exe命令运行某个主类。

回顾那个诡异的代码

从入口开始看

Singleton mysingleton = Singleton.GetInstence();

是根据内部类的静态方法要一个Singleton实例。这个时候就属于主动调用Singleton类了。之后内存开始加载Singleton类

1):对Singleton的所有的静态变量分配空间,赋默认的值,所以在这个时候,singleton=null、a=0、b=0。注意b=0是默认值,并不是我们手工为其赋予的的那个0值。(这一步对应连接阶段)

2):之后对静态变量赋初始值,这个时候的赋值就是我们在程序里手工初始化的那个值了。(这一步对应初始化阶段)

按照顺序,首先初始化静态变量singleton = new Singleton();调用了构造方法。构造方法里面a=1、b=1。之后接着顺序往下执行。

接下来public static int a;为a赋初始值,因为没有指定初始值,所以a保持不变,即a=1,

再之后public static int b = 0;为b赋初始值,代码中指定的初始值为0,此时b之前的1被0覆盖,因而b=0;  所以最终结果为a=1,b=0。

而如果将代码中静态属性的声明顺序改为如下:

public static int a;     

public static int b = 0;     

public static Singleton singleton = new Singleton();

那么先初始化a,a没有指定初始值,保持不变,a仍为默认值0,再初始化b,b的指定初始值为0,所以b=0,

最后初始化Singleton singleton = new Singleton();调用构造器,其中a++,b++,所以最终结果为a=1,b=1



原创粉丝点击