自工做以上,程序由于代码越写越多,变得愈来愈臃肿,效率也会变得愈来愈低,因而我总喜欢不断去优化程序结构外,内存优化和性能调优。java
要对Java程序进行内存优化和性能调优,须要了解虚拟机的内部原理,了解Java虚拟机的好处除了上述说起两点好处。从更深一点的技术层面上看,了解Java虚拟机的规范和实现,将更加有助于咱们编写高效、稳定的Java代码。好比,假如了解Java虚拟机的内存模型,了解虚拟机的内存回收机制,那么咱们就不会过度依赖它,而会在须要的时候显式的"释放内存"(Java代码不能显式释放内存,可是能够经过释放对象引用告知垃圾回收器回收该对象须要被回收),以下降没必要要的内存消耗;假如咱们了解Java栈的工做原理,那么咱们就能够经过减小递归层数,减小循环次数来下降堆栈溢出的风险。可能对于应用开发人员来讲,可能不会直接去涉及这些Java虚拟机底层实现的工做,可是了解这些背景知识,或多或少,都会对咱们写的程序产生潜移默化的好的影响。数组
本篇文章,将简明扼要的说明Java虚拟机的体系结构和内存模型,若有用词不妥或解释不许确之处,请不吝指正,深感荣幸!网络
Java 虚拟机体系结构多线程
类装载子系统ide
Java虚拟机有两种类装载器,分别是启动类装载器和用户自定义装载器。
通类装载子系统经过类的全限定名(包名和类名,网络装载还包括 URL)将 Class 装载进运行时数据区。对于每个被装载的类型,Java虚拟机都会建立一个java.lang.Class类的实例来表明该类型,该实例被放在内存中的堆区,而装载的类型信息则位于方法区,这一点和全部其余对象都是同样的。
类装载子系统在装载一个类型前,除了要定位和导入对应的二进制class文件外,还要验证导入类的正确性,为类变量分配并初始化内存,以及解析符号引用为直接引用,这些动做严格按照如下顺序进行:
装载——查找并装载类型的二进制数据;
链接——执行验证,准备以及解析(可选)性能
验证 确保被导入类型的正确性
准备 为类变量分配内存,并将其初始化为默认值
解析 把类型中的符号引用转换为直接应用
优化
方法区spa
对于每个被类装载子系统装载的类型,虚拟机都会保存下列数据到方法区:
类型的全限定名
类型超类的全限定名(java.lang.Object没有超类)
类型是类类型仍是接口类型
类型的访问修饰符
任何直接超接口的全限定名有序列表操作系统
除了上述基本类型信息,还将保存以下信息:
类型的常量池
字段信息(包括字段名、字段类型、字段修饰符)
方法信息(包括方法名、返回类型、参数的数量和类型、方法修饰符,若是方法不是抽象和本地的,还将保存方法的字节码、操做数栈和该方法栈帧中的局部变量区的大小和异常表)
常量之外的全部类变量(其实就是类的静态变量,由于静态变量是全部实例共享的,且与类型直接相关,因此他们是类一级的变量,做为类的成员被保存在方法区)
一个到类ClassLoader的引用线程
//返回的就是刚才保存的ClassLoader引用
String.class.getClassLoader();
一个到Class类的引用
//将返回刚才保存的Class类的引用
String.class;
注意,方法区也是能够被垃圾回收器回收的,当一个类型再也不被引用且方法区内存不足时,虚拟机将卸载该类型,回收内存。
堆
Java程序在运行时建立的全部类实例或数组都放在同一个堆中,而每个Java虚拟机也只有一个堆空间,全部线程将共享这一个堆(这就是一个多线程的Java程序会产生对象访问的同步问题的缘由了)。
因为每一种Java虚拟机都有对虚拟机规范的不一样实现,因此咱们可能不知道每一种Java虚拟机在堆中是以何种形式表示对象实例的,不过咱们能够经过下面这可能的实现来一窥端倪:
程序计数器
对于运行中的Java程序而言,每个线程都有本身的PC(程序计数器)寄存器,它是在该线程启动时建立的,大小为一个字长,用来保存须要被执行的下一行代码的位置。
Java栈
每个线程都有一个Java栈,以栈帧为单位保存线程的运行状态。虚拟机对Java栈的操做有两种:压栈和出栈,两者都已帧为单位。栈帧保存了传入参数、局部变量、中间运算结果等数据,在方法完成时被弹出,而后释放。
看一下两个局部变量相加时栈帧的内存快照
本地方法栈
这是 Java 调用操做系统本地库的地方,用来实现 JNI(Java Native Interface,Java 本地接口)
执行引擎
Java虚拟机的核心,控制装入 Java 字节码并解析;对于运行中的Java程序而言,每个线程都是一个独立的虚拟机执行引擎的实例,从线程生命周期的开始到结束,他要么在执行字节码,要么在执行本地方法。
本地接口
链接了本地方法栈和操做系统库。
注:文中全部提到"Java虚拟机"的地方都是指"JavaEE和JavaSE平台的Java虚拟机规范"。