关于java类加载

 1 JVM整体架构
根据JVM规范,JVM内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

JAVA类的加载过程

在Run的时候,先将.java文件编译成.class文件。然后,在通过类加载器,将class文件加载到JVM中,然后在运行。输出结果。

类加载的生命周期包括:加载Loading,验证Verification, 准备Preparation,解析Resolution, 初始化Initialization,使用Using和卸载Unloading.

Java中用import导入类和用Class方法加载类有什么区别

import仅仅包含导入操作,并不包含将字节码文件加载进内存这一动作,将字节码文件加载进内存是后续的实例化操作完成的。例如通过import导入了一堆包和类,但是后续什么都没用(没用实例化),那么导入的东西是不会被加载进内存的。而且import是编译期的,如果你在后续代码中没有使用到你导入的内容,那么import语句甚至不会编译和执行。查看字节码文件可以看出,import的作用就是对你程序中要用到(实例)的东西进行署名(signature),当程序运行的时候好知道你实例化的对象的类的字节码文件去哪里找。

而Class.forName方法包含的动作是:根据给出的全类名(方法的参数)找到对应的字节码文件,并将字节码文件通过ClassLoader加载进内存中生成Class类对象(方法的返回值就是Class类对象)。

A a = (A)Class.forName("pacage.A").newInstance();这和 A a =new A();是一样的效果

Class类的常用方法

1、getName() 

一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

2、newInstance()

Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:
    x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。

3、getClassLoader() 

返回该类的类加载器。

4、getComponentType() 
    返回表示数组组件类型的 Class。

5、getSuperclass() 
    返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。

6、isArray() 
    判定此 Class 对象是否表示一个数组类。

加载

加载阶段是将class文件从磁盘或者jar等读到JVM内存中,并为其创建一个Class对象。任何一个类被使用时候系统都会为其创建一个Class对象的。

验证

验证是连接阶段的第一步,这一阶段的目的是 为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并却不会危害虚拟机自身的安全。

按照虚拟机规范,如果验证到输入的字节流不符合Class文件的存储格式,就抛出一个java.lang.VerifyError异常或其子类异常。

大致分成4个阶段的验证过程: 文件格式验证、 元数据验证、 字节码验证和 符号引用验证

准备阶段
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个时候 内 存分配的仅包括类变量(static变量),不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“ 通常情况下”是数据类型的零值( 随后在初始化阶段生成定义的初值)。 如果该变量被final修饰,将在编译时生成ConstantValue,这样在准备阶段将直接设置成该初值

解析阶段

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

其实就是将堆内存空间里的静态变量符号修改为已经申请了空间的静态变量地址的过程

解析动作主要针对类/接口,字段,类方法,接口方法四类符号引用进行

可以确定如果成员变量没有new,是不会走的成员变量的类的初始化阶段。

初始化阶段

是类加载过程的最后一步,初始化阶段才真正开始执行类中定义的JAVA程序代码

准备阶段中,变量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的计划来赋值

或者说,初始化阶段是执行类构造器<clinit>()方法的过程

1.<clinit>()方法是由编译器自动收集类中的所有类变量的复制动作和静态语句块中的语句合并而成。编译器收集的顺序和语句在源文件中出现的顺序一致,静态语句块中只能访问到定义在它之前的变量,定义在它之后的变量,只能赋值,不能访问
2.<clinit>()方法与类的构造函数<init>()不同,不需要显式的调用父类构造器,虚拟机会保证父类的3.<clinit>()在子类的之前完成。因此,虚拟机执行的第一个<clinit>()方法肯定是java.lang.Object.
4.由于父类<clinit>()方法先执行,也就意味着父类中定义的静态语句要优先于子类的变量赋值操作。
5.<clinit>()方法并不是必须的,如果一个类没有静态语句块也没有对变量赋值操作,就不会生成
接口中不能使用静态语句块,但仍有变量初始化赋值的操作,因此也会生成<clinit>()方法,但与类不同的是,接口的<clinit>()方法不需要执行父接口的<clinit>()方法。只有当父几口中定义的变量被使用时,父接口才初始化,另外,接口的实现类在初始化时一样不会执行接口的<clinit>()方法。
6.虚拟机会保证一个类的<clinit>()方法在多线程环境中正确的加锁同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都会阻塞,直到该方法执行完,如果在一个类的<clinit>()方法中有耗时很长的操作,可能会造成多个进程阻塞,在实际应用中,这种阻塞往往很隐蔽。

思考:

1.Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段和静态变量!

2.Class.forName 方法是指加载阶段,还是加载+连接阶段?加载+连接阶段

3.new关键字和newInstance()方法的区别: 

1. newInstance: 弱类型。低效率。只能调用无参构造。  
2. new: 强类型。相对高效。能调用任何public构造。

4.class会被回收吗? 会!

5.<clinit>()方法和构造函数

问题:

假设子类A,父类B,A的成员变量C

import XXX.C;

class A extends B{

static long gStartTime = System.currentTimeMillis();

  C c=null;

public A(){

LogUtil.d("langxm","A classlocader "+ gStartTime);
LogUtil.d("langxm","B Constructor "+ System.currentTimeMillis());

public func(){

c = new C();

}

}

结果:

B classlocader

A classlocader

B Constructor

A Constructor s

A Constructor e

C classlocader

##从结果来看,如果先调用class.forName(), 那么因为gStartTime是静态变量,会在此函数中执行。会与B的Constructor 有时间差。

##C 出现在真正加载的地方,说明import和成员变量不赋值不会走class.forName()

##C的时间点,能说明成员变量是在A的函数调用前执行的吗?

##为什么A这个类第一次加载慢,第二次快,可能是内部的成员已经初始化class.forName过了

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值