浅谈Java类型装载、连接与初始化--转自https://blog.csdn.net/lincolnmi/article/details/50538016

类型装载、连接与初始化

Java虚拟机通过装载、连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用。其中装载就是把二进制形式的Java class文件读入Java虚拟机中去;连接把读入虚拟机的二进制形式的Java class文件合并到虚拟机的运行时的状态中去。连接可以分为三个子步骤,验证,准备和初始化。验证步骤确保了Java类型数据格式正确并且适于Java虚拟机使用。准备为该类型分配内存(如为类变量分配内存)。解析负责将常量池中的符合引用转换为直接引用,虚拟机的实现可以推迟解析这一步骤。即当运行中的程序真正使用某个符号的时候再去解析它。(符号引用变为直接引用)。初始化为类变量赋以适当的初始值。 
下面就这5个步骤进行详细的解析:

装载

装载阶段由三个动作完成

  1. 通过该类型的完全限定名,产生一个代表该类型的二进制数据流。 
    该二进制数据流可以遵循class文件格式,也可以遵守其他数据格式。所有虚拟机必须能识别Java class文件格式,但是个别的也可以识别其他二进制格式。 
    Java虚拟机并未规定该二进制数据流如何产生,因此下面列出了可能产生二进制数据的方式: 
    从本地文件系统装载class文件; 
    从网络下载一个class文件; 
    从ZIP,JAR,CAB中提取class文件; 
    从专有数据库中提取class文件; 
    将Java源文件编译为class文件; 
    使用上述方法,但是产生非class文件格式的二进制文件格式
  2. 解析这个二进制数据流为方法区的内部数据结构。
  3. 创建一个表示该类型的java.lang.class类的实例。

装载的最终目的就是创建java.lang.class的实例对象,解析二进制数据流为方法区的内部数据结构,并在堆上面建立一个Class对象。Class对象是Java程序与内部数据结构之间的接口。

Java类型要么被启动类装载器装载,要么被用户自定义的类加载器加载。 
类加载器并不是在某个类型首次主动使用的时候加载它。可以提前预判,预加载这个类型。如果预加载的时候出现问题,不会立即报错。直到程序主动使用该类才报错。也就是说程序已知不主动使用该类类型,类加载器就一直不报错。

装载时会做两项检查,虽然在验证阶段之前进行,但逻辑上属于验证阶段。

  1. 验证class文件格式是否正确,如魔数,每个部分在正确位置,正确的长度,文件的长度不是太长或太短等。 保证正式解析二进制class文件时候,不会造成虚拟机的崩溃。
  2. 确保每个除Object之外每个类都有一个超类,加载某个类的时候,确保该类的所有超类必须加载。

验证

类型被加载后,虚拟机开始连接,连接第一步是验证,即确认它是否符合Java语言的语义,并且不会危及虚拟机的完整性。 
验证的时机和方式,不同虚拟机有着不同的实现。虚拟机规范只是规定了虚拟机在验证过程中可以抛出的异常以及在何种情况下必须抛出它们。 
验证阶段往往会验证如下内容:

  1. 检查final的类不拥有子类
  2. 检查final方法不能被覆盖
  3. 确保类型和超类型之间没有不兼容的方法申明(比如两个方法签名完全一样,但返回类型不同)
  4. 检查所有的常量池入口相互之间保持一致(比如CONSTANT_String_info入口的string_index项目必须是一个CONSTANT_Utf8_info入口的索引)
  5. 检查常量池中所有的特殊字符串(类名,字段名,方法名,字段描述符和方法描述符)是否符合格式
  6. 检查字节码的完整性

上述任务最复杂的是最后一个,检查字节码的完整性。不能因为挑战指令超出了方法末尾导致了虚拟机崩溃。虚拟机必须在验证阶段检查出这样的字节码是非法的,从而抛出一个错误。 
不过虚拟机规范并未规定字节码验证必须在验证阶段执行,也可以在执行每条语句的时候单独进行验证。Java虚拟机指令集当初设定目标是字节码流一次性使用数据流分析器进行验证。不过运行时候进行验证,会使得Java程序运行速度提高。当使用数据流分析器进行字节码验证时候,可能为了确保符合Java语言的语义而装载其他的类。比如加载了Float类,必须还加载其父类Number保证其不是一个final类。

验证阶段之后可能还会进行一项检查,那就是符号引用的验证。虚拟机在将符号引用替换为直接引用,需要检查该元素的存在性以及访问的权限。逻辑上属于验证,但往往在解析阶段发生。

准备

在准备阶段,Java虚拟机为类变量分配内存,并设置默认的初始值。在正式初始化阶段之前,类变量都没有被正式初始化为真正的初始值。 
准备阶段,干的另外一件事就是可能为一些数据结构分配内存,进而提高程序运行的性能。比如方法表的建立,它包含了指向类中每一个方法包括从超类继承方法的指针。从而可以在执行继承方法时候不需要搜索超类。

解析

解析过程就是在类型的常量池中寻找类、接口、自动和方法的符合引用,把这些符号引用变成直接引用的过程。还记得逻辑上属于验证的符合引用的验证么?解析什么时候执行是虚拟机自己决定的,可以在初始化阶段后面执行。

初始化

初始化阶段为类变量赋予正确的初始值。在准备阶段,类变量被赋予了默认值,在这一阶段,类变量被赋予正确的初始值,即程序员希望这个类变量具备的起始值。 
具体的初始化过程参见下一篇博客《浅谈类加载的初始化阶段

原文地址:https://www.cnblogs.com/wood-lin/p/9019419.html

时间: 2024-10-12 11:53:19

浅谈Java类型装载、连接与初始化--转自https://blog.csdn.net/lincolnmi/article/details/50538016的相关文章

转-java编译时error: illegal character '\ufeff' 的解决办法-https://blog.csdn.net/t518vs20s/article/details/80833061

原文链接:https://blog.csdn.net/shixing_11/article/details/6976900 最近开发人员通过SVN提交了xxx.java文件,因发布时该包有问题需要回退,故SCM将该xxx.java文件用editplus打开删除了新添的一行,删除后重新编译打包,却报了如下异常: java:[1,0] illegal character: \65279 表面看着该文件确实没错,看不出来问题,后来从SVN上更新下代码以后,发现本地也不报错,后来通过Eclipse查看了

转:Java面试题集(51-70) http://blog.csdn.net/jackfrued/article/details/17403101

Java面试题集(51-70) Java程序员面试题集(51-70) http://blog.csdn.net/jackfrued/article/details/17403101 摘要:这一部分主要讲解了异常.多线程.容器和I/O的相关面试题.首先,异常机制提供了一种在不打乱原有业务逻辑的前提下,把程序在运行时可能出现的状况处理掉的优雅的解决方案,同时也是面向对象的解决方案.而Java的线程模型是建立在共享的.默认的可见的可变状态以及抢占式线程调度两个概念之上的.Java内置了对多线程编程的支

java Object类源代码详解 及native (转自 http://blog.csdn.net/sjw890821sjw/article/details/8058843)

Java代码  package java.lang; public class Object { /* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用.*/ private static native void registerNatives(); /* 对象初始化时自动调用此方法*/ static { registerNatives(); } /* 返回此 Object 的运行时类.*/ public final native Class<?> getClass();

JVM中java类的加载时机(转载:http://blog.csdn.net/chenleixing/article/details/47099725)

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制.类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(using).和卸载(Unloading)七个阶段.其中验证.准备和解析三个部分统称为连接(Linki

浅谈java类中成员的初始化顺序(一)

类被创建之后的成员的初始化顺序到底是怎么样的? 首先 不考虑继承 package com; public class DemoOne { /** * 关于类的初始化顺序 */ //不考虑继承结构的情况 private static int a=1; private String str="我被赋值了"; static{ //为什么static成员函数不能访问非static变量, 不能调用非static成员函数? //静态代码块独立于对象而存在 不依赖于对象存在 简单来说可以直接以类型名

Java语言的反射机制 笔记 摘自 http://blog.csdn.net/kaoa000/article/details/8453371

在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的.这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制. 1.Java 反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类.在运行时构造任意一个类的对象.在运行时判断任意一个类所具有的成员变量和方法.在运行时调用任意一个对象的方法 2.Reflection 是Java被视为动态(或准动态)语言的一个关键性质.

“微个日光里”对JAVA/IO基本的总结,在此保存方便复习,原文http://blog.csdn.net/xiebaochun/article/details/29559881

流 JAVA /IO 基本小结 通过一行常见的代码讨论:new BufferedReader(new InputStreamReader(System.in)) java的IO是基于流(stream)概念的,什么是流呢,作为初学者,我是这样理解的,在各个应用之间传送的是BITS,这些BIT可已被认为是流体,可以就认为是水流,那么用来在各个水源之间转移水的工具应该选择什么呢?一般情况下,水管是可以的,所以数据我将数据源比作水源,将流对象比作水管这样就有了对流的第一步认识,它再也不神秘了.对于流,我

Java的垃圾回收机制(转自:http://blog.csdn.net/zsuguangh/article/details/6429592)

1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的一个系统级线程会自动释放该内存块.垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃.当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用.事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片.由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,

java按照map的value排序 转载http://blog.csdn.net/tsingheng/article/details/7909861

java的TreeMap可以排序,只可惜是按照key来排序的,或者重写其他Map的排序算法也都是按照key来排序的,下面贴出来一个按照value排序的算法: [java] view plaincopy public class SortMap { public static void main(String[] args) throws Exception { // TODO code application logic here Map<String, Integer> myMap = ne