java虚拟机的存在就是为了一个任务——执行java程序。程序开始是他运行,结束时他停止。对应的,需要指出,在一台机器上运行了三个程序的话,就会有三个运行中的虚拟机(曾经一度错误的理解过)。
java虚拟机实例通过调用某个初始类的main()方法来运行一个Java程序。而这个main()方法必须是共有的(public)、静态的(static)、返回值为void,并且接受一个字符串数组作为参数。任何拥有这样一个main()方法的类都可以作为Java程序运行的起点,任何其他的线程都是由这个初始线程启动的。
在Java虚拟机内部有两种线程:守护线程和非守护线程。守护线程通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程。但是,Java程序也可以把它创建的任何线程标记为守护线程。而Java程序中的初始线程——就是开始于main()的那个,是非守护线程。 只要还有任何非守护线程在运行,那么这个Java程序也在继续运行。当该程序中所有的非守护线程都终止时,虚拟机实例将自动退出。假若安全管理器允许,程序本身也能够通过调用Runtime类或者System类的exit()方法来退出。
从另一个角度来说,C/C++等纯编译语言从源码到最终执行一般要经历:编译、连接和运行三个阶段,连接是在编译期间完成,而java在编译期间仅仅是将源码编译为Java虚拟机可以识别的字节码Class类文件,Java虚拟机对中Class类文件的加载、连接都在运行时执行,虽然类加载和连接会占用程序的执行时间增加性能开销,但是却可以为java语言带来高度灵活性和扩展性,java的针对接口编程和类加载器机制实现的OSGi以及热部署等就是利用了运行时类加载和连接的特性。
java的Class类在虚拟机中的生命周期依次包括如下几个步骤:加载(loading)—>验证(verifycation)—>准备(prepareation)—>解析(Resolution)—>初始化(initialization)—>使用(using)—>卸载(unloading)。上一段中我们包含的连接包括:验证、准备、解析三个步骤。另外需要指明的是:加载、验证、准备、初始化和卸载这个五个阶段的顺序是确定的,而解析阶段则不一定,在某些情况下为了支持java语言的运行时动态绑定,也可以在初始化阶段之后再开始。各个步骤操作如下:
(1)、加载:java虚拟机把Class类文件加载到内存中,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。
在加载阶段,java虚拟机需要完成以下3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。
b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。
c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口。
加载阶段与连接阶段是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,这些夹在加载阶段之中进行的动作仍然属于连接阶段,加载和连接阶段仍然保持着固定的先后顺序。
(2)、验证:验证是连接阶段的第一步,其目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全,如果验证失败,会抛出java.lang.VerifyError异常。
验证阶段的主要工作有:
a.文件格式验证:验证Class文件魔数、主次版本、常量池、类文件本身等等。
b.元数据验证:主要是对字节码描述的信息进行语义分析,包括是否有父类、是否是抽象类、是否是接口、是否继承了不允许被继承的类(final类)、是否实现了父类或者接口的方法等等。
c.字节码验证:是整个验证过程中最复杂的,主要进行数据流和控制流分析,如保证跳转指令不会跳转到方法体之外的字节码指令、数据类型转换安全有效等。
d.符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候(连接第三阶段-解析阶段进行符号引用转换为直接引用),符号引用验证的目的是确保解析动作能正常执行,如果无法通过符号引用验证,则会抛出java.lang.IncompatibleClassChangeError异常的子类异常,如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
验证阶段对于虚拟机来说非常重要,但是不是一个必需的阶段,如果所运行的代码已经反复被使用和验证过了,可以通过-Xverify:none参数关闭大部分的验证措施,以提高虚拟机时间效率。
(3).准备:准备阶段是正式为类变量(静态变量,注意不是实例变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。
特别注意:
对于普通非final的类变量,如public static int value = 123;在准备阶段过后的初始值是0(数据类型的零值),而不是123,而把123赋值给value是在初始化阶段才进行的动作。
对于final的类变量,即常量,如public staticfinal int value =123;在准备阶段过程的初始值直接就是123了,不需要准备为零值。
(4).解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(SymbolicReference):以一组符号来描述所引用的目标,与虚拟机内存布局无关,引用的目标不一定已经被加载到虚拟机内存中。
直接引用(DirectReference):可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机上翻译处理的直接引用不一定相同,如果有了直接引用,则引用的目标对象必须已经被加载到虚拟机内存中。
解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行解析。
(5).初始化:初始化是类使用前的最后一个阶段,在初始化阶段java虚拟机真正开始执行类中定义的java程序代码。
Java虚拟机规范严格规定了有且只有以下四种情况必须立即对类进行初始化:
a.遇到new、获取静态变量(final常量除外)、为静态变量赋值以及调用静态方法时,如果类没有进行过初始化,则需要先触发其初始化。
b.使用java.lang.reflect包的方法对类进行反射调用的时候(Class.forName(…)),如果类还没有初始化,需要先触发对其的初始化。
c.当初始化一个类的时候,如果发现其父类还没有初始化,则需要先触发对其父类的初始化。
d.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
上述四种情况称为对一个类的主动引用,除此之外的引用方式都不会触发初始化,称为被动引用。初始化的过程其实就是一个执行类构造器方法的过程,类构造器执行的特点和注意事项:
1).类构造器方法是由编译器自动收集类中所有类变量(静态非final变量)赋值动作和静态初始化块(static{……})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定。静态初始化块中只能访问到定义在它之前的类变量,定义在它之后的类变量,在前面的静态初始化中可以赋值,但是不能访问。
2).类构造器方法与实例构造器方法不同,它不需要显式地调用父类构造器方法,虚拟机会保证在调用子类构造器方法之前,父类的构造器方法已经执行完毕。
3).由于父类构造器方法先与子类构造器执行,因此父类中定义的静态初始化块要先于子类的类变量赋值操作。
4). 类构造器方法对于类和接口并不是必须的,如果一个类中没有静态初始化块,也没有类变量赋值操作,则编译器可以不为该类生成类构造器方法。
5).接口中不能使用静态初始化块,但可以有类变量赋值操作,因此接口与类一样都可以生成类构造器方法。
这里我们也可以考虑一下接口与类不同点:
首先,执行接口的类构造器方法时不需要先执行父接口的类构造器方法,只有当父接口中定义的静态变量被使用时,父接口才会被初始化。其次,接口的实现类在初始化时同样不会执行接口的类构造器方法。
(6).使用:
java虚拟机会保证一个类的方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的方法,其他线程都需要阻塞等待,直到活动线程执行方法完毕。初始化阶段,当执行完类构造器方法之后,才会执行实例构造器的方法,实例构造方法同样是按照先父类,后子类,先成员变量,后实例构造方法的顺序执行。当初始化完成之后,java虚拟机就可以执行Class的业务逻辑指令,通过堆中java.lang.Class对象的入口地址,调用方法区的方法逻辑,最后将方法的运算结果通过方法返回地址存放到方法区或堆中。
(7).卸载:当对象不再被使用时,java虚拟机的垃圾收集器将会回收堆中的对象,方法区中不再被使用的Class也要被卸载,否则方法区(Sun HotSpot永久代)会内存溢出。
Java虚拟机规定只有当加载该类型的类加载器实例为unreachable状态时,当前被加载的类型才被卸载.启动类加载器实例永远为reachable状态,由启动类加载器加载的类型可能永远不会被卸载,类型卸载仅仅是作为一种减少内存使用的性能优化措施存在的,具体和虚拟机实现有关,对开发者来说是透明的.
卸载自定义来加载器加载的类的可靠做法为:
a.每次创建特定类加载器的新实例来加载指定类型的不同版本,这种使用场景下,一般就要牺牲缓存特定类型的类加载器实例以带来性能优化的策略了.
b.对于指定类型已经被加载的版本, 会在适当时机达到unreachable状态,被unload并垃圾回收.每次使用完类加载器特定实例后(确定不需要再使用时), 将其显示赋为null, 这样可能会比较快的达到jvm 规范中所说的类加载器实例unreachable状态, 增大已经不再使用的类型版本被尽快卸载的机会.
程序猿行业技术生活交流群:181287753(指尖天下),欢迎大伙加入交流学习。
相关推荐
Applet的生命周期包括初始化、启动、绘画、停止和销毁五个阶段,这些阶段由JVM管理和控制。 在深入Java虚拟机的光盘中,可能包含了一系列关于JVM内部机制的文档、示例代码和演示程序,这些内容有助于开发者理解JVM...
【深入Java虚拟机(三)——类的生命周期(下)类的初始化1】 类的生命周期在Java中是一个关键的概念,它涵盖了从加载到卸载的整个过程。在类的生命周期中,初始化阶段是非常重要的,因为它涉及到类的静态变量的赋值...
【深入Java虚拟机(二)——类的生命周期(上)类的加载和连接】 Java虚拟机(JVM)是Java程序的核心,它负责解释和执行Java字节码。类的生命周期在JVM中是一个关键的概念,它涵盖了从类的加载到卸载的整个过程。...
《实战JAVA虚拟机 JVM故障诊断与性能...以上内容是《实战JAVA虚拟机 JVM故障诊断与性能优化》可能涉及的主要知识点,深入学习和实践这些内容,能帮助开发者成为更优秀的Java程序员,有效地处理各种JVM相关的复杂问题。
Java虚拟机(JVM)是Java程序运行的核心组件,它为Java代码提供了跨平台的运行环境。JVM的底层结构主要包括几个关键部分:寄存器、栈和堆。 首先,JVM寄存器是虚拟机内部的一个概念,尽管实际硬件可能不包含与之...
《实战Java虚拟机——JVM故障诊断与性能优化(第2版)》是Java开发者深入理解JVM工作原理、诊断问题以及进行性能调优的重要参考资料。该书籍的源码提供了丰富的示例和实践案例,帮助读者更好地掌握Java虚拟机的内部...
《KJava深入浅出——Java在PDA上的程序设计》一书主要涵盖了Java技术在掌上设备(PDA)上的应用开发,旨在帮助开发者理解和掌握KJava编程的基础知识和实战技巧。KJava是Java Micro Edition (Java ME)的一个分支,...
《深入JAVA虚拟机第二版》是一本专注于Java技术体系中核心部分——Java虚拟机(JVM)的权威著作。本书全面、深入地探讨了JVM的工作原理,是Java开发者提升技术水平,理解Java运行机制,优化代码性能的重要参考资料。...
新生代、老年代和持久代是堆内存的划分,不同的对象根据其生命周期被分配在相应区域。分代收集算法提高了垃圾回收的效率。 3. 方法区:方法区存储了类的元数据,包括类名、方法信息等。HotSpot JVM中的永久代(Perm...
总的来说,《MiniJavaVM——一个Java虚拟机的设计和实现》是一篇宝贵的教育资源,它通过实例化JVM的简化版本,使开发者能够深入理解Java程序的运行机制,掌握虚拟机设计的关键要素,从而在实际开发中更加得心应手。...
在《实战Java虚拟机——JVM故障诊断与性能优化》一书中,作者深入探讨了如何对JVM进行故障排查和性能调优,通过源码分析来帮助读者理解其内部工作原理。下面我们将根据书中的主题,详细阐述相关的知识点。 1. **JVM...
JVM(Java虚拟机)的工作原理,包括类加载机制、垃圾收集(GC)以及内存模型(JMM),都是面试中常见的主题。理解垃圾收集器如新生代、老年代、CMS和G1的工作方式,以及如何优化内存使用。 接下来,讨论并发和多...
本文将深入探讨Java线程的生命周期,包括创建、启动、结束以及线程的协作和调度。 首先,创建Java线程有两种主要方式:直接实例化`Thread`类或者创建一个新的类去继承`Thread`类并重写`run()`方法。例如,你可以...
本资源包含《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》一书的书签目录、源码,旨在帮助读者更全面地学习和实践JVM相关的高级特性和最佳实践。 1. **内存管理** - **堆内存**:Java对象主要存储在堆...
《Java虚拟机(第二版)》一书涵盖了Java开发与运行环境的核心部分——Java虚拟机(JVM)的深入解析。这本书旨在帮助读者理解JVM的工作原理,优化Java应用程序的性能,并解决运行时可能出现的问题。 Java自1995年...
标题"开源的javacard虚拟机实现"揭示了本文主要关注的是一个特定的开源项目——OpenJCVVM,这个项目实现了JavaCard虚拟机,使得开发者可以免费获取源代码并对其进行定制、改进或深入研究。在智能卡领域,开源的...
这一特性主要得益于Java的字节码和Java虚拟机(JVM)的概念。Java源代码被编译成字节码(.class文件),这是一种中间语言,不依赖特定的操作系统或硬件,具有高度的可移植性。字节码在不同的平台上通过Java运行时...
Applet不依赖于传统的main()方法,而是由Web浏览器中的Java虚拟机负责调用执行。由于Applet是从远程服务器下载并在本地运行,为了确保安全,Applet会在一个称为“沙箱”的环境中运行,这限制了它对本地系统的访问...
- **内存区域**:包括堆、栈、方法区、本地方法栈、程序计数器等,各有不同的作用和生命周期。 - **垃圾回收**:Java自动进行内存管理,垃圾回收器自动回收不再使用的对象。 9. **反射和注解** - **反射**:在...