第7章 类型的生命周期
java虚拟机通过装载、连接和初始化一个java类型,使该类型可以被正在运行的java程序所使用。
装载:是把二进制形式的java类型读入java虚拟机中。
连接:是把读入的二进制形式的类型数据合并到虚拟机的运行时状态中去。连接分三个子步骤(验证、准
备和解析)。验证步骤确保java类型数据格式正确并且适合于java虚拟机使用;准备步骤则负责为
该类型分配它所需的内存,并将类变量设置为默认值;解析步骤负责把常量池中的符号引用转换为
直接引用。虚拟机的实现可以推迟解析这一步,它可以在程序真正使用某个符号引用时再去解析
它。
初始化:当验证、准备和解析(可选)步骤都完成时,该类型就已经为初始化做好了准备,在初始化期
间,将给类变量赋以适当的初始值。
初始化时机:所有的虚拟机实现必须在每个类或接口首次主动使用时初始化。
符合主动使用的情形:
(1) 当创建某个类的新实例时(在字节码中执行new指令,不明确的创建:反射、clone和反序列化)。
(2) 当调用某个类的静态方法时(在字节码中执行invokestatic指令)。
(3) 当使用某个类或接口的静态字段,或者对该字段赋值时(在字节码中执行getstatic或putstatic),用
final修饰的静态字段除外,它被初始化为一个编译时的常量表达式。
(4) 当调用java API中的某些反射方法时,比如Class类中的方法或者java.lang.reflect包中的类的方法。
(5) 当初始化某个类的子类时(类初始化时,要求它的超类已经被初始化)。
(6) 当虚拟机启动时某个被标明为启动类的类(含有main方法的那个类)。
除了这6种情形外,所有其他使用java类型的方式都是被动使用,它们不会导致java类型的初始化。
任何一个类的初始化都要求它的超类在此之前已经完成初始化;然而,对于接口来说,这条规则并不适用。只有在某个接口所声明的非常量字段被使用时,该接口才会被初始化,而不会因为实现这个接口的子接口或类要初始化而被初始化。而且一个接口的初始化,并不要求它的祖先接口预先初始化。
装载阶段:由三个基本动作组成,要装载一个类型,java虚拟机必须:
(1) 通过该类型的完全限定名,产生一个代表该类型的二进制数据流。
(2) 解析这个二进制数据流为方法区的内部数据结构(虚拟机实现相关的内部数据结构)。
(3) 在堆中创建一个表示该类型的java.lang.Class类的实例。
验证阶段:连接过程的第一步是验证,确认类型符合java语言的语义,并且它不会危及虚拟机的完整性。
准备阶段:在准备阶段,java虚拟机为类变量分配内存,设置默认初始值;但在到达初始化阶段之前,类
变量都没有被初始化为真正的初始值(准备阶段不会执行java代码)。在准备阶段,java虚拟
机实现也可能为一些数据结构分配内存,比如方法表。
解析阶段:解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换
为直接引用的过程。
初始化:通过类变量初始化语句或静态初始化语句为类变量赋予正确的初始值。java源码中所有的类变量
初始化语句和静态初始化块都被java编译器收集到一起,放到一个特殊的方法中。对于类来说,
这个方法被称作类初始化方法;对于接口来说,它被称为接口初始化方法。在类和接口的class文
件中,这个方法被称为<clinit>。java程序是无法调用这个方法的,它只能被虚拟机调用,专门用
于为类型的静态变量设置正确的初始值。
java虚拟机必须确保初始化过程被正确地同步。如果多个线程需要初始化一个类,仅仅允许一个线程来进行初始化,其他线程需要等待。当活动的线程完成了初始化过程之后,它必须通知其他等待的线程。
初始化步骤:
(1) 如果类存在直接超类,且直接超类还没有被初始化,就先进行直接超类的初始化(第一个被初始化
的类永远是Objec)。初始化接口并不需要初始化它的父接口。
(2) 如果类或接口存在一个初始化方法,就执行此方法(<clinit>)。
<clinit>()方法的代码并不显式地调用超类的<clinit>()方法。在java虚拟机调用类的<clinit>()方法之前,它必须确认超类的<clinit>()方法已经被执行了。
并非所有的类都需要在它们的class文件中拥有一个<clinit>()方法。如果类没有声明任何类变量,也没有静态初始化语句,那么它就不会有<clinit>()方法。如果类声明了类变量,但是没有明确使用类变量初始化语句或者静态初始化语句,那么类也不会有<clinit>()方法。如果类仅包含static final变量的初始化语句,而且这些类变量的初始化语句采用编译时常量表达式,类也不会有<clinit>()方法。只有那些的确需要执行java代码来赋予类变量正确初始值的类才会有类初始化<clinit>()方法。
所有在接口中声明的隐式 public static final字段都必须在字段初始化语句中初始化。如果接口包含任何不能在编译时被解析成一个常量的字段初始化语句,接口就会拥有一个<clinit>()方法。
主动使用和被动使用:使用一个非常量的静态字段,只有当类或者接口的确声明了这个字段是才是主动使用。比如,类中声明的静态字段可能会被子类引用,对于子类来说就是被动使用,使用它们不会触发子类的初始化。
如果一个字段是static final的,并且使用一个编译时常量表达式初始化,使用这样的字段就不是对声明该字段的类的主动使用。java编译器把这样的字段解析成对常量的本地拷贝(该常量存在于引用者类的常量池或者字节码流中)。
public class PassiveUse { public static void main(String[] args) { System.out.println(NewBaby.hoursOfSleep); System.out.println(NewBaby.greeting); } static { System.out.println("PassiveUse is initialized"); } } class NewParent { static int hoursOfSleep = (int) (Math.random() * 3.0); static { System.out.println("NewParent is initialized"); } } class NewBaby extends NewParent { static final String greeting = "hello world"; static int hoursOfCry = (int) (Math.random() * 2.0); static { System.out.println("NewBaby is initialized"); } }
输出为:
PassiveUse is initialized
NewParent is initialized
1
hello world
类实例化:类可以被明确或者隐含地实例化,实例化一个类有四种途径:
(1) 明确地使用new操作符。
(2) 调用Class或者java.lang.reflect.Construtor对象的newInstance()方法。
(3) 调用任何现有对象的clone()方法。
(4) 通过java.io.ObjectInputStream类的getObject()方法反序列化。
@SuppressWarnings("unused") public class ClassInstantiation { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, CloneNotSupportedException { Example obj1 = new Example("new"); Class<Example> myClass = Example.class; Example obj2 = myClass.newInstance(); Example obj3 = (Example) obj1.clone(); } } @SuppressWarnings("all") class Example implements Cloneable { Example() { System.out.println("create by newInstance()"); } Example(String msg) { System.out.println("create by " + msg); } protected Object clone() throws CloneNotSupportedException { return super.clone(); } } 输出为: create by new create by newInstance()
当java虚拟机创建一个类的新实例时,首先都需要在堆中为保存对象的实例变量分配内存。所以在对象的类中和它的超类中声明的变量(包括隐藏的实例变量)都要分配内存。一旦虚拟机为新的对象准备好了堆内存,它立即把实例变量初始化为默认的初始值。随后虚拟机就会为实例变量赋正确的初始值。
根据创建对象的方法不同,java虚拟机使用三种技术之一来完成这个工作。
如果对象通过clone()调用创建的,虚拟机把原来被克隆的实例变量中的值拷贝到新对象中。
如果对象是通过readObject()调用反序列化的,虚拟机通过从输入流中读入的值来初始化那些非transient类型的实例变量。
否则,虚拟机调用对象的实例初始化方法<init>。
java编译器为它编译的每一个类都至少生成一个实例初始化方法。在class文件中这个实例初始化方法被称为<init>。针对源码中类的每一个构造方法,java编译器都产生一个<init>()方法。如果类没有明确声明任何构造方法,编译器默认产生一个无参数的构造方法,它仅仅调用超类的无参构造方法;编译器在class文件中创建一个<init>()方法,对应它的默认构造方法。
如果构造方法中通过明确的this()调用另一个构造方法。它对应的<init>()方法由两部分组成:一个同类与this()参数相同的<init>()方法的调用;实现了对应构造方法的方法体的字节码。
如果构造方法不是通过this()调用开始的(代码第一行不是this()),而且这个类型不是Object,<init>()方法则由三部分组成:一个超类的无参<init>()方法调用(如果是Object,这项不存在);任意实例变量初始化语句的字节码;实现了对应构造方法的方法体的字节码。
如果构造方法通过明确的super()开始,它的<init>()方法会调用对应参数类型的超类<init>()方法;任意实例变量初始化语句直接码;实现了对应构造方法的的方法体的字节码。
对于除Object外的每一个类,<init>()方法都必须从另一个<init>()方法调用开始。<init>()方法不允许捕捉由它们所调用的<init>()方法抛出的任何异常;如果超类的<init>()方法被意外中止了,那么子类的<init>()方法也必须同样被意外中止。
卸载类型:java虚拟机判断一个动态装载的类型是否仍然被程序需要,其方式与判断对象是否仍然被程序需要的方式很类似。如果程序不再引用某个类型,那么这个类型就无法在对未来的计算过程产生影响。类型编程不可触及的,而且可以被垃圾收集。
判断动态装载的类型的Class实例在正常的垃圾收集过程中是否可触及有两种方式:
(1) 如果程序保存对Class实例的明确引用,它就是可触及的。
(2) 如果在堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例,那么这个Class实
例就是可触及的。
相关推荐
本书十三个章节,分别讲解了android系统基础知识,android系统的结构和核心框架,Java虚拟机和Dalvik虚拟机的知识,实现程序编译和调试,Dalvik的运作流程,DEX优化和安全管理,Android虚拟机生命周期的管理和内存...
深入理解 Java 虚拟机笔记 Java 虚拟机(JVM)是 Java 语言的运行环境,它负责解释和执行 Java 字节码。下面是 Java 虚拟机相关的知识点: 虚拟机内存结构 Java 虚拟机的内存结构主要包括以下几个部分: * 方法...
虚拟机栈的生命周期与线程相同,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 堆区是 JVM 的最大的一块内存区域,是被线程共享的区域,在虚拟机启动时创建。所有类的实例...
Java虚拟机(JVM)是运行Java字节码的虚拟环境,它位于操作系统之上,硬件之下,提供了一层软件抽象,使得Java程序可以在多种平台上运行而无需重新编译。JVM的核心功能包括内存管理、垃圾收集、安全性和平台独立性。...
### 学习深入理解Java虚拟机的前几章笔记 #### JVM内存模型 Java虚拟机(JVM)的内存模型主要分为两大类:线程共享区和线程私有区。 ##### 线程共享区 - **堆**:是所有线程共享的内存区域,在这里存放着对象实例...
### 深入Java虚拟机JVM类加载学习笔记 #### 一、Classloader机制解析 在Java虚拟机(JVM)中,类加载器(ClassLoader)是负责将类的`.class`文件加载到内存中的重要组件。理解类加载器的工作原理对于深入掌握JVM以及...
java类加载的机制,包括类加载的生命周期,还有每个生命周期的详细解析
新生代主要用于存放生命周期短的对象,占堆内存的1/3,其中又细分为Eden区(8/10)、From Survivor区(1/10)和To Survivor区(1/10)。对象首先在Eden区创建,经过一系列GC过程,存活下来的对象会被转移到Survivor...
Java虚拟机栈同样是线程私有的,其生命周期与线程相同。它用来描述Java方法执行的内存模型,每个方法被执行时都会创建一个栈帧来存储局部变量表、操作数栈、动态链接、方法出口等信息。方法执行过程即为栈帧的入栈和...
* 优化代码:使用 null 显式赋值、虚引用等方式及时回收大对象,减少大对象的生命周期,检查数据结构使用是否合理等。 4. JVM 对象创建 JVM 对象创建过程包括: * 类加载:类加载完毕后,其对象所需内存大小是...
- **新生代**:主要用来存放生命周期较短的对象。根据“大部分对象很快死亡”的原则,新生代采用了复制算法来提高垃圾收集的效率。新生代又细分为一个Eden区和两个Survivor区(S0、S1),默认比例为8:1:1。 - **...
笔记会详细解析Activity、Fragment的生命周期,以及如何避免内存泄漏和提高应用性能。 最后,笔记可能包含一些高级主题,如自定义View、动画、多进程通信(AIDL)、插件化技术、热修复以及最近的Jetpack组件库的...
在内存管理方面,笔记会讲解Java的垃圾回收机制,包括对象的生命周期、引用类型以及内存泄漏问题。此外,还会介绍JVM(Java虚拟机)的工作原理,包括类加载机制、内存区域划分以及性能优化策略,这对于理解Java程序...
静态变量存储在方法区,生命周期与类的加载和卸载同步。 变量的分类在Java中分为局部变量和成员变量。成员变量又细分为实例变量(非静态)和静态变量。实例变量属于对象,而静态变量属于类。静态变量在类加载时被...
笔记可能会深入讲解如何创建和使用类,以及理解对象的生命周期。 3. **异常处理**:Java的异常处理机制是其强大之处,笔记可能会涉及try-catch-finally结构,异常类型,自定义异常等。 4. **集合框架**:Java集合...
- 局部变量:定义在方法或语句块内的变量,生命周期与该方法或语句块相同。 - 成员变量:定义在类内部、方法外部的变量,与对象关联。 - 静态变量:定义时使用`static`关键字,与类关联。 8. **常量**: - 初始...
在进行Java学习时,理解类和对象的生命周期,包括创建、初始化、使用和销毁,是非常重要的。同时,深入理解内存管理,特别是垃圾回收机制,将有助于编写出更高效、更稳定的程序。另外,学习如何利用Java的标准库,如...