Java内存区域与内存溢出异常
运行时数据区域
程序计数器:字节码的行号指示器;每条线程都需要有一个独立的程序计数器;“线程私有”;唯一没有OOMError情况的区域。
Java虚拟机栈(Hot Spot本地方法栈):Java方法执行的内存模型;“线程私有”;局部变量表存放了各种基本类型、对象引用、returnAddress类型;抛出SOFError和OOMError;单线程下只抛出SOFError,不断建立线程可以产生OOMError。
Java堆:存放对象实例;抛出OOMError异常。
方法区(1.7):存储已被虚拟机加载的类信息、常量、静态常量、即时编译器变异后的代码等数据;1.8后类元数据放到本地内存、常量池和静态变量放到 Java 堆;抛出OOMError异常。
HotSpot虚拟机对象
对象的创建:对象规整内存分配方式“指针碰撞”;对象不规整内存分配方式:“空闲列表”;内存分配方式取决于垃圾收集器回收内存时是否有整理功能。
保障对象内存分配线程安全两种方法:分配时采用循环CAS保证原子性;本地线程分配缓冲(TLAB)。
对象的内存结构:对象头、实例数据和对齐填充;对象头一部分用于存储对象自身的运行时数据(哈希码、GC年代、锁相关信息)、一部分为类型指针;实例数据存储字段内容;对齐填充保证对象大小是8字节的整数倍。
对象的访问定位:通过句柄访问对象的栈里存储句柄地址,句柄池里存储到对象实例的指针(指向堆)和到对象类型数据的指针(指向方法区或本地内存);通过直接指针访问对象的栈里存储的实例对象的指针,实例对象对象头里存储到对象类型数据的指针。
垃圾收集器与内存分配策略
回收对象判断
引用计数法:计算引用数量,有引用时就不回收;实现简单;难以解决循环引用。
可达性分析算法:从GC Roots节点开始搜索,对象不可达则回收;GC Roots包括虚拟机栈中应用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈JNI引用的对象。
强引用:GC永不回收。
软引用:SoftReference;系统将要发生内存溢出异常之前进行二次回收。
弱引用:WeakReference;下一次GC回收。
虚引用:PhantomReference;对象被GC时收到一个系统通知。
回收方法区:回收废弃常量和无用的类;该类无实例、ClassLoader被回收、对应的java.lang.Class对象无引用的类可以被回收。
垃圾收集算法
标记清除算法:效率问题;空间问题。
复制算法(新生代):解决效率问题;Eden和Survivor的大小比例是8:1;分配担保。
标记整理(老年代):解决空间问题。
HotSpot算法实现
枚举根节点:OopMap存放对象引用位置帮助可达性分析;
安全点:安全点才能暂停开始GC;安全点太少会使得GC等待时间太长;太长会生成大量OopMap增大运行负荷。
安全区域:在一段代码中引用关系没有变化,扩展的安全点。
抢先式中断:GC停止所有线程,不在安全点的线程恢复并跑到安全点再停止。
主动式中断:各线程在安全点或创建对象需要分配内存的地方轮询中断标志位,自己主动中断。
垃圾收集器
Serial\Serial Old收集器:新生代复制;老年代标记-整理;都需要“STW”;对于Client模式下的虚机是个好选择。
ParNew\ParNew Old收集器:多线程版Serial;Server模式下的首选新生代GC收集器(唯一可以与CMS配合的)。
Parallel Scavenge收集器:复制算法;CMS等收集器的关注点是尽可能地缩短“STW”时间(适合交互程序),Parallel Scavenge收集器的目的是达到一个可控制的吞吐量(适合后台程序)。
CMS收集器:初始标记、并发标记、重新标记、并发清除四个步骤;初始标记和重新标记需要“STW”;初始标记只标记能和GC Roots直接关联的对象;并发标记一边全局搜索一边记录引用变化;重新标记将全局搜索与引用变化的对象做并集;并发清除进行GC;CMS占用CPU较高;无法处理浮动垃圾;需要碎片整理。
G1收集器:并行与并发;分代收集;空间整合;可预测的停顿;将整个Java堆划分为多个大小相等的独立区域,优先回收价值最大的区域;初始标记、并发标记、最终标记、筛选回收四个步骤(与CMS类似);
Remembered Set:用以存放堆的其他部分对本部分的引用以减少对其他部分GC Roots的搜索(新生代存放老年代对其的引用)(G1区域存放其他区域的引用);以空间换时间。
内存分配与回收策略
对象优先在Eden分配;大对象直接进入老年代;长期存活的对象进入老年代。
动态对象年龄判定:如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代。
空间分配担保:Minor GC之前,虚机先检查老年代最大可用空间是否大于新生代所有对象总空间,若大于则Minor GC,若不大于检查参数是否允许担保失败,如果允许则检查老年代最大可用空间是否大于新生代晋级老年代的平均大小,若大于则Minor GC,否则Full GC。
类文件与类加载机制
类文件
类文件:平台无关性;语言无关性;8位字节为基础单位的二进制流。
类加载的时机
类的生命周期:加载、验证、准备、解析、初始化、使用、卸载。
触发类初始化:遇到new、getstatic、putstatic、invokestatic这4条字节码命令时;reflect方法反射调用时;初始化子类的时候父类还没初始化(接口在被使用时才会初始化);虚拟机启动时初始化主类;动态语言支持解析出REF_getStatic、REF_putStatic、REF_invokeStatic的句柄并且对应的类没有初始化时。
类加载的过程
加载:加载器通过类的全限定名获取定义此类的二进制字节流(从ZIP包、网络、运行时计算生成、其他文件生成、数据库中读取);将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
数组加载:不通过类加载器创建、由虚拟机直接创建;引用类型数组递归加载组件类型、在加载该组件类型的类加载器的类名称空间上被标识;基本类型数组与引导类加载器关联;数组可见性与组件可见性一致(基本类型为public)。
验证:虚拟机对自身保护的一项重要工作;文件格式验证(验证字节流是否符合Class文件格式的规范)、元数据验证(对类的元数据信息进行语义校验)、字节码验证(通过数据流和控制流分析确定程序语义是合法符合逻辑的、通过StackMapTable优化)、符号引用验证(确保解析动作能正常执行);-Xverify:none关闭大部分类验证措施、缩短虚拟机类加载时间。
准备:正式为类变量在方法区中分配内存并设置类变量初始值(static)。
解析:将常量池内的符号引用替换为直接引用;符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中;直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄;虚拟机可以根据需要判断在类被加载器加载时就对常量池中的符号引用进行解析还是等到一个符号引用将要被使用前才去解析。
初始化:执行类构造器方法(编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的)的过程;编译器收集的顺序是由语句在源文件中出现的顺序决定的;虚拟机会保证子类类构造器方法执行之前父类的方法已经执行完毕;类构造器方法不是必须的;执行接口的类构造器方法不需要先执行父接口的类构造器方法,只有当静态变量被使用时父接口才会初始化;虚拟机会保证类构造器方法在多线程环境中被正确地加锁、同步(单例的线程安全实现方法之一)。
类加载器
类加载器:实现通过一个类的全限定名来获取描述此类的二进制流这一动作的代码模块;由加载类的类加载器和这个类本身一同确立类在Java虚拟机中的唯一性;启动类加载器(Bootstrap ClassLoader)由C++实现,虚拟机的一部分;其他类加载器由Java语言实现,独立于虚拟机外部,全部继承自抽象类java.lang.ClassLoader;其他类加载器包括扩展类加载器(负责加载<JAVA_HOME>\lib\ext目录中的或java.ext.dirs系统变量所指定的路径中的所有类库、开发者可直接使用)、应用程序类加载器(负责加载用户类路径上所指的类库,开发者可直接使用)。
双亲委派模型:除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,父子关系使用组合实现;如果一个类加载器收到了类加载请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成;线程上下文加载器、OSGi模块化热部署不遵守双亲委派模型。
虚拟机字节码执行引擎
运行时栈帧结构
栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构,虚拟机运行时数据区中的虚拟机栈中的栈元素;每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程;栈帧包括局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息;在活动线程中位于栈顶的栈帧才是有效的,成为当前栈帧;当前栈帧相关联的方法称为当前方法。
局部变量表:用于存放方法参数和方法内部定义的局部变量;实例方法局部变量表的第0位索引Slot默认是用于传递方法所属对象实例的引用(this)。
操作数栈:先入后出栈;方法的执行过程中会有各种字节码指令往操作数栈中写入和提取内容(出栈、入栈)。
动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
方法返回地址:正常完成出口中PC计数器的值作为返回地址;异常完成出口(抛异常)返回地址通过异常处理器表来确定;方法退出的过程等同于把当前栈帧出栈。
附加信息:一些规范里没有描述的信息;动态连接、方法返回地址与其他附加信息全部归为栈帧信息。
方法调用
解析:调用目标在程序代码写好、编译器进行编译时就必须确定下来;静态方法、私有方法、实例构造器、父类方法在类加载的时候就会把符号引用解析为该方法的直接引用。
静态分派:静态类型决定重载版本;所有依赖静态类型来定位方法执行版本的分派动作称为静态分派;静态分派发生在编译阶段。
动态分派:在运行期根据实际类型确定方法执行版本的分派过程。
单分派多分派:Java是一门静态多分派、多态单分派的语言。
虚拟机动态分派的实现:为类在方法区建立一个虚方法表,虚方法表中存放着各个方法的实际入口地址。
基于栈的字节码解释执行引擎
基于栈的指令集架构:指令流中的指令大部分都是零地址指令,依赖操作数栈进行工作;可移植;执行速度稍慢。
编译期优化
Javac编译器
编译过程:解析与填充符号表过程、插入式注解处理器的注解处理过程、语义分析与字节码生成过程。
解析:包括词法分析和语法分析两个过程;词法分析是将源代码的字符流转变为标记集合;语法分析是根据Token序列构造抽象语法树的过程;抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表这程序代码中的一个语法结构。
填充符号表:是一组符号地址和符号信息构成的表格;符号表中所登记的信息在编译的不同阶段都要用到。
注解处理器:在编译期间对注解进行处理;可以读取、修改、添加抽象语法树中的任意元素。
语义分析:语义分析分为标注检查以及数据及控制流分析;标注检查步骤检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等;常量折叠;数据及控制流分析是对程序上下文逻辑更进一步的验证;数据及控制流分析可以检查出诸如程序局部变量在使用前是否赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正常处理等问题。
解语法糖:语法糖指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用;在编译阶段将语法糖还原简单的基础语法结构称为解语法糖。
字节码生成:不仅是把前面各个步骤所生成的信息转化为字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作;生成类构造器;代码替换优化程序。
语法糖
泛型与类型擦除:参数化类型;Java实现为伪泛型。
自动装箱、拆箱与遍历循环:使用最多的语法糖。
条件编译:根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除。
运行期优化
HotSpot虚拟机内的即时编译器
解释器与编译器:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行;解释器把越来越多的代码编译成本地代码可以获取更高地效率;HotSpot虚拟机中内置了两个即时编译器分别为Client Compiler(C1)和Server Compiler(C2);混合模式、编译模式、解释模式、分层编译模式(C1和C2同时工作)。
编译对象及触发条件:编译对象为被多次调用的方法、被多次执行的循环体。热点检测方法分别为基于采样的热点探测(检查栈顶)和HotSpot采用的基于计数器的热点探测(为每个方法建立计数器统计执行次数)。
基于计数器的热点探测方法:方法调用计数器和回边计数器;方法调用计数器在方法被调用后计数器加1、和回边计数器计数和超过阈值后进行编译、过一段时间后计数减半;回边计数器的作用是统计一个方法中循环体代码执行的次数、和方法调用计数器计数和超过阈值后进行编译、计数不会衰减。
C1编译过程:简单快速、局部优化;一个平台独立的前端在完成一部分基础优化(如方法内联、常量传播)后将字节码构造成一种高级中间代码表示;一个平台相关的后端在高级中间代码上完成另外一下优化(如空值检查消除、范围检查消除)后生成低级中间代码表示;在平台相关的后端使用线性扫描算法在低级中间代码上分配寄存器并做窥孔优化,然后产生机器码。
c2编译过程:充分优化过的高级编译器;会执行所有经典的优化动作和一些与Java语言特性密切相关的优化技术。
编译优化技术
公共子表达式消除:语言无关的经典优化技术之一;用计算结果直接代替重复的公共子表达式,避免重复计算。
数组边界检查消除:语言相关的经典优化技术之一;省略一些不必要的数组边界检查操作。
方法内联:最重要的优化技术之一;消除方法调用的成本、为其他优化手段建立良好的基础。
逃逸分析:最前沿的优化技术之一;为其他优化手段提供依据的分析技术;证明一个对象不会逃逸到方法或线程之外,则可以进行栈上分配、同步消除、标量替换等优化。
Java内存模型与线程
Java内存模型
主内存与工作内存:所有的变量都存储在主内存中;每条线程有自己的工作内存;工作内存中保存了被该线程使用到的变量的主内存副本拷贝;线程对变量的所有操作都必须在工作内存中进行。
内存间交互操作:Java内存模型定义了8种操作来完成主内存与工作内存的交互。
volatile型变量的特殊规则:可见性、有序性;读前加内存屏障,写后加内存屏障;
原子性:synchronized保证大范围的原子性。
可见性:volatile、synchronized、final保证可见性。
有序性:volatile、synchronized保证有序性。
happens-before:Java内存模型定义的两项操作之间的偏序关系;程序次序规则、管程锁定规则、volatile变量规则、线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性。
Java与线程
线程的实现:内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。
线程状态:新建;运行;无限期等待;限期等待;阻塞;结束。
线程安全与锁优化
线程安全
线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
线程安全程度:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。
互斥同步:synchronized、ReentrantLock。
非阻塞同步:CAS;Atomic;
无同步方案:可重入代码;线程本地存储。
锁优化
自旋锁与自适应锁:让线程不挂起,等待释放锁从而节约挂起恢复线程的资源;由前一次在同一个锁上的自旋时间及锁的拥有者来决定本次自旋的等待时间。
锁消除:即时编译器运行时对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除;锁消除的主要判定依据来源于逃逸分析的数据分析。
锁粗化:虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部。
轻量级锁:使用CAS在没有多线程竞争的前提下减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
偏向锁:消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。