前言
从Hello World!到现在,写了这么久的Java代码,Java虚拟机于我从来都只是静静地存在jdk的目录中的工具。我每天在它上面跑代码,调试代码,却很少去仔细研究它到底是怎么运作起来的,和它打交道也仅限于-Xms,-Xmx,在不了解这些配置的真正含义是什么的情况下很难去真正的记住这些模糊的名称。了解Java虚拟机的运行原理,可以帮助我们更好地了解程序运行时到底在做什么、是怎么做到的,同时对我们更深入地了解Java这门语言有着重要的帮助。
Java虚拟机是什么
毫无疑问,Java虚拟机可以用来运行我们使用javac编译过后的class字节码文件,经过main函数后得到我们想要的运行结果。 虽然叫Java虚拟机,但JAVA虚拟机不光只支持Java语言编译而来的class文件,它还可以识别由其他语言编译出来的class文件,如JRuby,Jyphon,Groovy等。只要开发语言可以编译成能被JAVA虚拟机正确解析的的字节码格式,那么他们都可以被Java虚拟机执行。
Java代码是如何运行的
class文件在经历
-
加载:通过类加载器加载来自各种方式(文件、网络、压缩包、动态生成等)生成的字节码流,将这些信息按照格式存储在方法区之中,并在内存中创建一个Class对象用于访问这些信息的入口。
-
连接:包括验证、准备和解析三步。 ——在验证步骤中,虚拟机会校验文件的数据结构是否正确,继承、重载、重写等是否符合规范,以及一些指令的语义校验,以确保字节流能够正确地解析,不会对虚拟机构成安全威胁。 ——进入到准备阶段后,由我们定义在代码中的类变量(static修饰)将会被初始化为零值(不同类型数据的零值不同),真正的赋值过程要等到
方法被执行时才会被执行。但常量(final)会被直接初始化。 ——解析过程会将定义在常量池中的符号引用(如类的全限定名,方法名称及描述符,字段的名称及描述符)替换为直接引用(在内存中的指针,偏移量或者句柄),解析可以在类加载时就进行,也可以在运行时调用到相应指令再进行。 -
初始化:执行由编译器自己生成的
方法来完成初始化, 由代码中的静态块、和对静态变量的赋值语句组合而来。执行子类的初始化方法会由虚拟机保证先执行父类的初始化方法,故父类的静态块和赋值会先于子类执行。且虚拟机通过同步的方式保证该方法只会被执行一次。
内存是如何管理的
自动内存管理+垃圾回收机制是Java虚拟机最提供的最方便易用的特性,它让我们在编码的同时不用去考虑内存的分配和销毁问题,想要对象只需要自己new一个就可以使用,当对象不再需要时,它就有可能会被GC自动回收。
Java的内存管理在给我们带来编码的便利性时也带来了性能的问题,当虚拟机中管理的对象过多时,进行一次Full GC带来的“Stop The World”开销可能会长达几十秒。针对不同的场景,主流的商用Java虚拟机提供了分代垃圾回收算法供我们选择使用,其中新生代和老年代使用的收集算法不同:
新生代:
- Serial 收集器:单线程收集器,使用复制算法,在收集时暂停所有用户线程是Client模式虚拟机默认收集器。[CMS,Serial Old]
- Parallel New收集器:Serial的多线程版本收集器。[CMS,Serial Old]
- Parallel Scavenge:以吞吐量(用户代码运行时间与CPU总执行时间的比值)优先收集器,可以高效地利用CPU时间。采用复制算法。[Parallel Old, Serial Old]
老年代:
- CMS:以暂停时间最短优先的收集器,多线程,使用标记清除算法的收集器。基本上可以与用户线程并行。
- Serial Old:单线程收集器,使用标记-整理算法。主要用于Client模式虚拟机。
- Parallel Old:多线程,使用标记整理算法的收集器。
附:深入理解Java虚拟机 第二版 提取码 uv2s