0x00
先摘抄一段话:
和那些在编译时需要进行连接工作的语言不同,在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,这可能会导致类加载时增加了性能开销,不过这也为Java程序提供了高度的灵活性,Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。这个特性导致了很多有用的功能的出现,比如,可以编写一个面向接口的应用程序,等到运行时再指定接口的实现类;用户可以通过Java预定义的和自定义的类加载器,让一个本地的应用程序可以在运行时从网络或者其他地方加载一个二进制流作为程序代码的一部分。
大一C语言课的余老师说过,用VC6.0运行程序,要依次点击编译-连接-运行按钮。原来Java是的类加载和连接是在运行期间完成的。
下面是类的生命周期:
类的生命周期
0x01 类加载的时机
JVM规范中并没有约束类加载时机。但是遇到下面5种情况,类必须开始加载了:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有进行过初始化,就需要进行类的初始化。
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,就需要先进行类的初始化;
- 当初始化一个类的时候,如果发现父类还没有进行过初始化,则需要先初始化父类;
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类;
- 动态语言某些特殊句柄没有被初始化。
注意:
- 调用类的静态字段,只有直接定义这个字段的类才会被加载和初始化。通过其子类来引用父类中定义的字段,只会触发父类的初始化而不会触发子类的初始化。
2.调用类的静态常量是不触发类的加载过程。如果在A类中调用B类的静态常量,那么在编译阶段会将该静态常量放到A的Class文件的静态常量池中,所以对该常量的调用不涉及B的加载。
Dalvik VM (DVM) 与Java VM (JVM)之间有哪些区别?
0x02 类的加载过程
加载,验证,准备,解析,初始化
0x03 类加载器
1)ClassLoader
「通过一个类的全限定名获取描述此类的二进制字节流」这个动作是放到虚拟机外部实现的,实现这个动作的代码叫作「类加载器」,所以应用程序可以自己决定怎么获取所需要的类,比如Android里可以自定义ClassLoader实现从插件化动态加载sd卡上的class。
每个ClassLoader,都有独立的namespace。That is to say,
比较两个类是否相等,必须是这两个类由同一个ClassLoader加载的才有意义。否则,即便两个类来自同一个Class文件,被同一个VM加载,只要ClassLoader不同,两个类就必定不相等(eqauls/instanceof判断不相等)。
2)双亲委派模型
从Java虚拟机角度讲,只有两种ClassLoader,一种是BootStrap ClassLoader,是虚拟机的一部分;另一种独立于VM之外的ClassLoader,都继承自java.lang.ClassLoader。
在Android系统启动的时候会创建一个Boot类型的ClassLoader实例,用于加载一些系统Framework层级需要的类,我们的Android应用里也需要用到一些系统的类,所以APP启动的时候也会把这个Boot类型的ClassLoader传进来。
我老是把「双亲」理解成两个ClassLoader。。其实只是parent而已,就是当前classloader的父类,因为中文只能翻译成这样子。
双亲委派模型要求除了顶层的启动类加载器,其余的类加载器都应该有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承关系来实现,而是使用组合关系来复用父加载器的代码(但我看Android的DexClassLoader用的是继承而不是组合..)。
双亲委托模型的工作过程:如果一个类加载器收到了类加载器的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜说范围中没有找到所需的类时,子加载类才会尝试自己去加载)。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处是Java 类随着他的类加载器一起具备了一种优先级的层次关系。
例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
双亲委派模型对于保证Java 程序的稳定性很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader 的 loadClass() 方法中:先检查类是否被加载过,若没有则调用父加载器的loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载器失败,抛出 ClassNotFoundException 异常后,再调用自己的 finClass() 方法进行加载。
实现:
image.png
0x04 后记2017/12/08
今天考虑了一个问题,如果调用一个类的静态变量,会不会导致这个类被加载到内存/初始化?加载了之后什么时候被回收?
查了一些资料。
- 调用一个类的静态变量,是会让这个类被加载和初始化的。
- 加载、验证、准备、初始化,其中准备阶段会把static变量加载到方法区。
准备阶段
正式为类变量分配内存并设置类变量的默认值,这些内存在方法区中进行分配。内存分配仅针对类变量(static变量),不包括实例变量,实例变量是在对象实例化时和对象一起在堆中分配内存。
- 类是存放在java堆中的。
加载阶段
加载阶段虚拟机主要完成以下三件事:
1)根据类的路径,定位并获取类的class文件
2)通过加载器加载class文件,并将class文件里所代表的静态存储结构转化为方法区的运行数据结构
3)在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
- 在使用完static变量后,这个类就可以被回收(卸载)了。因为没有引用(=)传递。
对于类加载过程中的使用阶段,在类是实例化过程就是对class的一个使用,属于使用阶段。类被加载、连接和初始化后,它的生命周期就开始了。在虚拟机的生命周期中,始终不会被卸载。生命周期的结束意味着当前类对象或类成员没有引用指向它们,则虚拟机开始调用垃圾回收机制,清理类对象和类信息,类卸载完成。
ref:
http://blog.csdn.net/zjl477595675/article/details/48101611
<深入理解Java虚拟机>
网友评论