1. 运行时数据区域
1.1 程序计数器
a、 线程私有的内存区域
b、可以看作是当前线程所执行的字节码的行号指示器,通过它来取下一条需要执行的指令c、 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。 因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储d、此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域
1.2 java虚拟机栈
a、线程私有的内存区域b、 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 用于存储局部变量表、 操作数栈、 动态链接、 方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程c、 这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度(无限递归调用),将抛出 *Error 异常;如果虚拟机栈可以动态扩展 ,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常
1.3 本地方法栈
a、线程私有的内存区域b、 本地方法栈( Native Method Stack )与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
c、与虚拟机栈一样,本地方法栈区域也会抛出*Error和OutOfMemoryError异常
1.4 java堆
a、线程共享的内存区域b、 几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域c、主流虚拟机按照可扩展来实现的(通过-Xmx和-Xms控制)。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常
1.5 方法去
a、线程共享的内存区域b、用于存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据c、也会进行垃圾回收, 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载d、 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常
1.5.1 运行时常量池
a、运行时常量池是方法区的一部分b、用于存放编译期生成的各种字面量和符号引用c、 运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性, Java 语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中d、当常量池无法再申请到内存时会抛出OutOfMemoryError异常
1.6 直接内存
a、直接内存不是Java虚拟机规范中定义的内存区域b、但是java也有使用: 在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据c、如果管理员设置 各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常
2. 对象创建概述
2.1 虚拟机遇到一条new指令
a、检查这个指令的参数是否能在常量池中定位到一个类的符号引用b、检查这个符号引用代表的类是否已被加载、 解析和初始化过。 如果没有,那必须先执行相应的类加载过程。对象所需内存的大小在类加载完成后便可完全确定c、分配内存:指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离
空闲列表:虚拟机维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录如果对象A与对象B同事分配怎么办?分配动作同步处理或者每个线程在java堆上预先分配一小块内存,满了再同步处理d、初始化零值e、设置这个对象是哪个类的实例、元数据、哈希码、GC分代年龄等f、调用内置对象的<init>方法进行初始化
2.2 对象内存布局
a、对象在内存中存储的布局分为3块区域:对象头、实例数据和对齐填充
b、对象头包括两部分信息:第一部分用于存储对象自身的运行时数据,包括如哈希码、GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等第二部分数据是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
c、 实例数据部分是对象真正存储的有效信息
d、对齐填充并不是必然存在的,仅仅起着占位符的作用。VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果实例数据没有对齐,则通过它进行补齐
2.3 对象的访问定位
栈上的reference引用如何定位堆上的对象?
使用句柄方式: Java 堆中将会划分出一块内存来作为句柄池, reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据(Java堆)与类型数据(方法区)各自的具体地址信息
直接指针访问:就是直接访问
3. 垃圾回收机制
3.1 如何确定对象已死?
a、引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。无法解决对象A与对象B相互引用问题(大部分虚拟机不使用此算法)
b、可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象
3.2 引用类型
a、作用:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象
b、强引用:只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。Object obj=new Object()
c、软引用: 在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。SoftReference
d、弱引用:被弱引用关联的对象只能生存到下一次垃圾收集发生之前。WeakReference
e:虚引用: 完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,唯一目的就是能在这个对象被收集器回收时收到一个系统通知。PhantomReference
3.3 回收标记
a、如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。 当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”
b、GC将对F-Queue(finalize()方法对象的队列)中的对象进行第二次小规模的标记
c、finalize()只会被调用一次、不建议使用,可用try catch代替
3.4 回收方法区
a、常量池:回收废弃常量与回收Java堆中的对象类似,没有对象引用即可回收
b、类回收:java堆中不存在实例、加载该类的classloader被回收、该类的class对象没有被引用
3.5 垃圾回收算法
a、标记-清楚算法:见名知意。缺点:效率不高、会产生空间碎片,碎片太多会导致大对象无法分配
b、复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。简单高效、但可用内存缩小了一半,可使用8:1:1进行分配
c、 标记 - 整理算法:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
d、分代收集:新声代(复制)与老年代(标记),两个分别使用不同的算法。
3.5 垃圾回收器
a、Serial收集器(新生代,client端):单线程进行垃圾回收工作,Stop The World
b、ParNew收集器(新生代、server端):多线程进行垃圾回收工作,Stop The World
c、 Parallel Scavenge 收集器(新生代):与ParNew差不多,但可控吞吐量
d、Serial Old收集器(老年代):单线程进行垃圾回收工作,Stop The World
e、Parallel Old收集器(老年代):不解释
f、CMS(Concurrent Mark Sweep)收集器:最短回收停顿时间为目标。四个阶段:初始标记、并发标记、重新标记、并发清除。采用标记-清除算法。
e、G1(Garbage-First)收集器:四个阶段:初始标记、并发标记、最终标记、帅选回收。采用标记-整理算法。
4. 内存分配策略
4.1 内存分配策略
a、对象优先在Eden(新生代)分配。
b、大对象直接进入老年代:大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组
c、 长期存活的对象将进入老年代
d、动态对象年龄判定
e、空间分配担保
5. Class类文件的结构
5.1 Class类文件说明
a、任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)
b、Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中
c、 Class文件格式中只有两种数据类型:无符号数和表
d、无符号数以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、 索引引用、 数量值或者按照UTF-8编码构成字符串值
e、表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾
5.2 Class文件格式说明
a、magic:0xCAFEBABE(咖啡的一个品牌)
b、minor_version:次版本号
c、major_version:主版本号。 高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件
d、constant_pool_count:常量池容量计数器。从index 1开始计数,index 0表示特殊含义:不引用任何一个常量池数目
e、constant_pool:常量池。主要存放两大类常量:字面量和符号引用。字面量接近Java语言层面的常量,如文本字符串、 声明为final的常量值等。 符号引用包括下面三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符f、access_flags:标识类或者接口层次的访问信息
g、this_class:当前类名,u2类型的数值表示常量池的index(指向class_info常量类型)。h、super_class:父类名,u2类型的数值表示常量池的index(指向class_info常量类型),Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类i、interface_count、interface:表示count个接口,内容与表示法与前两项一致j、field_info:描述接口或者类中声明的变量。 字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。需要包括以下信息:字段的作用域(public、private、protected修饰符)、 是实例变量还是类变量(static修饰符)、 可变性(final)、 并发可见性(volatile修饰符,是否强制从主内存读写)、 可否被序列化(transient修饰符)、 字段数据类型(基本类型、 对象、 数组)、 字段名称k、method_info:描述接口或者类中声明的方法。字方法表的结构如同字段表一样l、attribute_info:太tm多了,下次再说
5.3 常量池说明
a、常量池中每一项常量都是一个表,目前共有14个常量表结构。14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位
b、utf8_info结构:
tag是标志位,length表示字符串所占字节长度,接着跟length个字节(有的字符要用2个或者3个字节表示)。u2类型能表达的最大值65535, 所以Java程序中如果定义了超过64KB英文字符的变量或方法名,将会无法编译
c、所有的结构
5.4 字段表与方法表说明
a、字段表结构(方法表也一样)
b、access_flags与类的access_flags类似,具体不太说明
c、 name_index和descriptor_index。 它们都是对常量池的引用,分别代表着字段的简单名称以及描述符
d、描述符的作用是用来描述字段的数据类型、 方法的参数列表(包括数量、 类型以及顺序)和返回值
e、描述符来描述方法时,按照先参数列表,后返回值的顺序描述,方法java.lang.String toString()的描述符为“()Ljava/lang/String;”方法int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int argetCount,int fromIndex)的描述符为“([CII[CIII)I”
5.5 属性表
a、属性表与常量表类型,先数量,后具体表结构。
b、java se 7已经有21项属性,不列举了,记不住。。。不行,还是挑两个说一下,有点重要
c、 属性名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可
5.5.1 Code属性
a、方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内
b、code表结构
c、 attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为“Code”,它代表了该属性的属性名称,attribute_length指示了属性值的长度,由于属性名称索引与属性长度一共为6字节,所以属性值的长度固定为整个属性表长度减去6个字节
d、max_stack代表了操作数栈深度的最大值。 虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
e、max_locals代表了局部变量表所需的存储空间,单位为Slot(可重用),长度不超过32位的变量用一个Slot,64的用两个。内部存储以下内容:方法参数(包括实例方法中的隐藏参数“this”)、异常处理参数、局部变量
f、code_length和code用来存储Java源程序编译后生成的字节码指令(方法不允许超过65535条字节码指令,即使使用了u4)
g、异常信息,暂不讲~
6. 字节码指令简介
6.1 字节码指令说明
a、一个字节长度,跟随其后的零至多个代表此操作所需参数
b、由于Java虚拟机采用面向操作数栈,所以大多数的指令都不包含操作数
c、缺点:一个字节(0~255),限制了总数、超过一个字节的操作数需要特殊处理d、优点:短小精干e、执行模型do {
自动计算 PC 寄存器的值加 1 ;
根据 PC 寄存器的指示位置,从字节码流中取出操作码;
if (字节码存在操作数)从字节码流中取出操作数;
执行操作码所定义的操作;
} while (字节码流长度> 0 );
6.2 加载和存储指令
a、将一个局部变量加载到操作栈:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
b、将一个数值从操作数栈存储到局部变量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
c、 将一个常量加载到操作数栈: bipush 、 sipush 、 ldc 、 ldc_w 、 ldc2_w 、 aconst_null 、 iconst_m1 、 iconst_ < i >、 lconst_ < l >、 fconst_ < f >、 dconst_ < d >
d、为什么没有byte,boolean、short、char类指令?编译器会动态扩展为int数据,从而减少指令集e、指令中<n>表示什么?把操作数隐含在指令之中, 例如 iload_0 的语义与操作数为 0 时的 iload 指令语义完全一致
6.3 方法调用指令
a、invokevirtual指令用于调用对象的实例方法
b、invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
c、 invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、 私有方法和父类方法
d、 invokestatic 指令用于调用类方法( static 方法)
e、 invokedynamic 指令用于在运行时动态解析出调用点限定符所引用的方法
6.4 同步指令
a、同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义
7. 类加载机制
7.1 什么时候进行初始化?
a、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
b、使用java.lang.reflect包的方法对类进行反射调用的时候
c、 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
d、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
e、当使用JDK 1.7的动态语言支持时,调用方法的句柄的类没有初始化,则需要有限进行初始化
f、前面的阶段呢?虚拟机没有强制约束前面的几个阶段,但是严格规定了只有以上五中情况会进行初始化,而加载、验证、准备、解析需要在之前进行。
7.2 加载
a、通过一个类的全限定名来获取定义此类的二进制字节流
b、使将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
c、 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
d、加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义
7.3 验证
a、目的:确保Class文件的字节流中包含的信息符合当前虚拟机的要求
b、文件格式验证:验证字节流是否符合Class文件格式的规范。魔数、版本号、常量池等
c、 元数据验证:对字节码描述的信息进行语义分析。是否有父类、是否实现抽象方法、字段和方法是否与父类矛盾等
d、字节码验证:确定程序语义是合法的、 符合逻辑的。操作数类型是否正确、跳转指令是否正确、类型转换是否有效等
e、符号引用验证:对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。是否能找到对应的类、对应的类中是否有对应的方法、方法是否可用等
7.4 准备
a、准备阶段是正式为类变量分配内存并设置类变量初始值(0,null)的阶段,这些变量所使用的内存都将在方法区中进行分配
b、如果是常量,则初始值为非0
7.5 解析
a、虚拟机将常量池内的符号引用替换为直接引用的过程,主要针对类或接口、 字段、 类方法、 接口方法、 方法类型、 方法句柄和调用点限定符
b、符号引用:以一组符号来描述所引用的目标(常量池中)
c、 直接引用:直接引用可以是直接指向目标的指针、 相对偏移量或是一个能间接定位到目标的句柄
7.5.1 类或接口的解析
a、如果不是一个数组类型,虚拟机将会把符号引用的全限定名传递给类加载器(当前类的)去加载这个类
b、如果是一个数组类型,并且数组的元素类型为对象,则先按照a加载元素,再由虚拟机生成数组对象
c、此时符号引用已经转换成直接引用,然后再判断访问权限
7.5.2 字段解析(类方法、接口方法也几乎一致)
a、先解析字段表中所属的类C
b、如果C包含了简单名称和字段描述符都与目标相匹配的字段,则查找结束
c、否则,如果在C中实现了接口,将会从下往上递归搜索各个接口和它的父接口
d、否则,如果C不是java.lang.Object的话,将会从下往上递归搜索其父类
e、否则,查找失败,抛出java.lang.NoSuchFieldError异常
f、查找过程成功返回了引用,将会对这个字段进行权限验证
7.6 初始化
a、类初始化阶段是类加载过程的最后一步,真正开始执行类中定义的Java程序代码
b、执行<clinit>():编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生
c、 < clinit >与类的构造函数 不同,它不需要显式地调用父类构造器,可以确保父类已执行完毕
d、父类中定义的静态语句块要优先于子类的变量赋值操作
e、执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法
f、<clinit>()方法在多线程环境中被正确地加锁、 同步,同一个类加载器下,一个类型只会初始化一次(类似单例)
8. 类加载器
8.1 类与类加载器
a、类加载器:通过一个类的全限定名来获取描述此类的二进制字节流(在虚拟机外部实现)
b、任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性
c、 比较两个类是否 “ 相等 ”(equals,isInstance,instanceof) ,只有在这两个类是由同一个类加载器加载的前提下才有意义
8.2 双亲委派模型
a、启动类加载器:虚拟机自身的一部分,无法被Java程序直接引用。用来加载负责将存放在<JAVA_HOME>\lib目录中,并且是虚拟机识别(文件名)的类b、扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录中的类c、应用程序加载器:程序默认的加载器,负责加载用户类路径ClassPath下的类
d、双亲委派模型伪代码:protected synchronized Class<?>loadClass(String name,boolean resolve)throws ClassNotFoundException {/ /首先,检查请求的类是否已经被加载过了 Class c=findLoadedClass(name); if(c==null){ try{ if(parent!=null){ c=parent.loadClass(name,false); }else{ c=findBootstrapClassOrNull(name); }} catch(ClassNotFoundException e){ //如果父类加载器抛出ClassNotFoundException //说明父类加载器无法完成加载请求 }i f(c==null){ //在父类加载器无法加载的时候 //再调用本身的findClass方法来进行类加载 c=findClass(name); }}i f(resolve){ resolveClass(c); }r eturn c; }e、某些情况下会破坏双亲模型:兼容1.2之前的jdk(重写某个方法),加载三方厂家的类(启动类加载器不认识),程序动态性(热替换、热部署)
9. 栈帧
9.1 栈帧说明
a、虚拟机栈的栈元素
b、包括局部变量表、 操作数栈、 动态连接、 方法返回地址和一些额外的附加信息
c、 编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈已经确定(方法表的Code)
9.2 局部变量表
a、用于存放方法参数和方法内部定义的局部变量(大小为Code属性的max_locals)
b、局部变量表的容量以变量槽(下称Slot)为最小单位
c、 虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从 0 开始至局部变量表最大的 Slot 数量(实例变量的第0位是“this”)
d、局部变量表中的Slot是可以重用的
9.3 操作数栈
a、元素可以是任意的Java数据类型(深度为Code属性的max_stacks)
b、操作数栈中元素的数据类型必须与字节码指令的序列严格匹配
9.4 动态连接
a、栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
9.5 方法返回地址
a、两种方式退出方法:正常退出,异常退出(无返回值)
b、方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态(比如返回调用者的PC计数器的值)
c、退出时执行的操作: 恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整 PC 计数器的值以指向方法调用指令后面的一条指令
10. 方法调用
10.1 说明
a、能被invokestatic和invokespecial指令调用的方法(还有被final修饰的方法),类加载的时候就已经解析(符号引用转换成直接引用)b、Java语言是一门编译期静态多分派(可根据多个参数选择)、 运行期动态单分派的语言
10.2 方法静态分派
a、代码示例:public class StaticDispatch{ static abstract class Human{ } static class Man extends Human{ } static class Woman extends Human{ } public void sayHello(Human guy){ System.out.println("hello,guy!"); } public void sayHello(Man guy){ System.out.println("hello,gentleman!"); } public void sayHello(Woman guy){ System.out.println("hello,lady!"); } public static void main(String[]args){ Human man=new Man(); Human woman=new Woman(); StaticDispatch sr=new StaticDispatch(); sr.sayHello(man); sr.sayHello(woman); } }运行结果:hello,guy! hello,guy!
10.3 invokevirtual指令的解析过程
a、找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
b、C中找到了方法,且权限验证通过,查找结束
c、 否则,按照继承关系从下往上依次对 C 的各个父类进行第 2 步的搜索和验证过程
d、如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常
10.4 运行期如何确定实际调用对象的?
a、其中一种方式是方法表结构:实现了方法的对象的指向自己的方法,否则指向父类的方法
10.5 基于栈的解释器执行过程
a、java代码:
public int calc() { int a=100; int b=200; int c=300; return(a+b)*c; }
b、相应的字节码:
public int calc(); Code: Stack=2,Locals=4,Args_size=1 0:bipush 100 2:istore_1 3:sipush 200 6:istore_2 7:sipush 300 10:istore_3 11:iload_1 12:iload_2 13:iadd 14:iload_3 15:imul 16:ireturn }c、执行过程:
11. 编译期
11.1 javac编译过程
a、解析与填充符号表过程
b、插入式注解处理器的注解处理过程
c、语义 分析与字节码生成过程
11.2 解析与填充符号表
a、词法(分解代码)、 语法分析(构建语法树)
b、填充符号表
c、 符号表 由一组符号地址和符号信息构成的表格,用于语义检查和地址分配
11.3 注解处理器
a、编译期间对注解进行处理,可以读取、 修改、 添加抽象语法树中的任意元素
b、如果对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止
11.4 语义分析与字节码生成
a、标注检查:检查的内容包括诸如变量使用前是否已被声明、 变量与赋值之间的数据类型是否能够匹配等
b、数据及控制流分析:检查程序局部变量在使用前是否有赋值、 方法的每条路径是否都有返回值、 是否所有的受查异常都被正确处理了等问题
c、 解语法糖:泛型、变长参数、 自动装箱/拆箱等
d、字节码生成:把前面生成的信息(语法树、 符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作(实例构造器<init>方法和类构造器<clinit>方法)
11.5 语法糖
a、泛型与类型擦除:ArrayList<int>与ArrayList<String>是同一个类,因为编译后把int与String都转成Object了
b、自动装箱、 拆箱:在编译之后被转化成了对应的包装和还原方法,如本例中的Integer.valueOf()与Integer.intValue()方法
c、遍历循环则把代码还原成了迭代器的实现,这也是为何遍历循环需要被遍历的类实现Iterable接口的原因
d、 变长参数,它在调用的时候变成了一个数组类型的参数
e、条件编译:根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉
12. 运行期
12.1 解释器与编译器
a、解释器与编译器两者优势:当程序需要迅速启动和执行的时候,解释器可以省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器把越来越多的代码编译成本地代码之后,可以获取更高的执行效率
12.2 编译对象
a、被多次调用的方法
b、被多次执行的循环体
12.3 编译优化技术
a、方法内联
b、公共子表达式消除
c、复写传播
d、无用代码消除(数组边界检查等等)
13. Java内存模型
13.1 内存模型规则
a、所有的变量(实例字段、 静态字段和构成数组对象的元素,不包括线程私有的)都存储在主内存中
b、线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
c、线程对变量的所有操作(读取、 赋值等)都必须在工作内存中进行
d、 不同的线程之间也无法直接访问对方工作内存中的变量
13.2 内存间交互操作
a、lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态
b、unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来
c、read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
d、load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
e、use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎
f、assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
g、store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用
h、write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
i、不允许read和load、 store和write操作之一单独出现
j、变量在工作内存中改变了之后必须把该变化同步回主内存
k、不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中
l、 一个新的变量只能在主内存中 “ 诞生 ” ,不允许在工作内存中直接使用一个未被初始化( load 或 assign )的变量
m、一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁
n、如果对一个变量执行lock操作,那将会清空工作内存中此变量的值
o、对一个变量执行unlock操作之前,必须先把此变量同步回主内存中
13.3 volatile
a、保证此变量对所有线程的可见性(当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的)
b、禁止指令重排序优化(内存屏障)
c、内存屏障:把修改同步到内存时,意味着所有之前的操作都已经执行完成,就不法重排序了
d、volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢一些
13.4 原子性、 可见性与有序性
a、原子性:基本数据类型的访问读写是具备原子性的(例外就是long和double的非原子性协定,不用考虑)。synchronized(内部就是lock和unlock)也可以保证原子性
b、可见性:volatile、synchronized和final都可以保证可见性
c、有序性:volatile和synchronized可以保证有序性。
14. 线程的实现
14.1 线程
a、把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、 文件I/O等),又可以独立调度(线程是CPU调度的基本单位)
b、 Java 使用的线程调度方式就是抢占式调度(系统分配线程执行时间),不是协同式调度(线程主动通知)
14.2 线程的实现
a、使用内核线程(轻量级进程)实现:代价高,需要用户态与内核态来回切换;消耗内核资源
b、使用用户线程实现:快速、低消耗、支持更多的线程,但实现困难(阻塞处理、处理器分配),一般没人使用
c、使用用户线程加轻量级进程混合实现:轻量级进程则作为用户线程和内核线程之间的桥梁,用户线程用于处理逻辑
14.3 线程的状态
a、新建(New):创建后尚未启动的线程处于这种状态
b、运行(Runable)
c、无限期等待(Waiting)
d、限期等待(Timed Waiting)
e、阻塞(Blocked):
f、结束(Terminated)
h、状态转换:
15. 线程安全
15.1 线程安全级别
a、不可变:不可变的对象一定是线程安全的。final修饰的对象,string对象等
b、 绝对线程安全:不管运行时环境如何,调用者都不需要任何额外的同步措施
c、相对线程安全:java中大部分线程安全对象都属于这种情况
d、线程对立
15.2 线程安全的实现方法
a、synchronized:重量级操作,大家应该都会用
b、ReentrantLock:与synchronize相似,但可以实现一下功能:等待可中断(等待线程中断)、 可实现公平锁(按顺序锁定),以及锁可以绑定多个条件
c、非阻塞同步:先进行操作,如果没有其他线程争用共享数据,那操作就成功;产生了冲突再补救。
d、无同步方法:要保证线程安全,并不是一定就要进行同步,两者没有因果关系。例如:可重入代码(不依赖共有资源、不调用非重入方法)、线程本地存储(ThreadLocal)
16. 锁优化
16.1 什么自旋锁、轻量级锁、偏向锁,没看懂,用到了再看。
笔记到此结束(-。-)