版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
JVM的自动内存管理机制
一如何划分JVM内存
NM所管理的内存在运行时会被分为这样几个数据区:虚拟机栈区,堆区,方法区,本地方法栈,程序
计数器
程序计数器是•个比拟小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,每条线程都
需要有一个独立的程序计数器,各条线程之间程序计数器互不影响,独立存储,是线程隔离的。程序计
数帮所在的内存区域是唯个在Java虚拟机标准中没有规定任何OulOfMemoryError情况的区域。
虚拟机栈,线程私有,它的生命周期与线程相同。虚拟机栈区描述的是Java方法执行内存模型:每个
无法在执行的同时都会创立一个栈帧用于存储局部变量、操作数栈、动态链接、方法出口等信息。每一
个方法从调用直至执行完成的过程,就对应着一个枝帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了8种根本数据类型、对象的引用和retumAddress。局部变量表所需的内存空间在编译
期间完成分配,在方法运行期间不会改变局部变量表的大小。
本地方法栈,作用与虚拟机栈区是相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法效劳,
而本地方法栈那么为虚拟机使用到的Native方法效劳。
堆,Java堆,也称GC堆,是最大的一块,是被线程共享的区域,在虚拟机启动时创立。所有类的实例
〔对象)利数组都是在堆上分配内存的,堆内存由存活和死亡的对象,空闲碎片区组成,对象所占的堆
内存是由自动内存管理系统回收。(数组是一种对象)
从内存回收角度来看,Java堆还可以细分为新生代和老年代;甚至还可以分为Eden空间、FromSurvivor
空间、ToSurvivor空间等。
从内存分配角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)o
Java堆可以处于物理上不连续的内存中,只要逻辑上连续即可。
方法区在JVM中也是一个非常直要的区域,在HotSpot虚拟机上,方法区被称为“永久代工虽然Java
虚拟机标准把方法区描述为堆区的一个逻辑局部,但还是要区分来对待。方法区用于存储已被JVM加载
的类信息[包括类的名称、方法信息、字段信息)、类变量〔静态变量)、常量、即时编译器编译后的代
码等数据。虽然方法区中有些数据是线程隔离的,但.是编译器编译后的代码等数据,是线程共享的。
除了和Java堆一样不需要连续的内存和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。但不
并非方法区就不要内存回收了,方法区的内存回收只要针对常量池的I可收和对类型的卸载。
运行时常量池,是方法区的一局部。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还
有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。
二对象的创立
在语言层面上,创立对象通常仅仅是一个new关键字而己。但在虚拟机中,对象的创立过程大致分为以
下四步:
第一步,检查类加载。虚拟机遇到•条new指令时,首先需要去检查这个指令的参数是否能在常量池中
定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,
那必须先执行相应的类加载过程。
第二步,分配内存。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小
在类加载完成后便可完全确定。分配方式大致有两种:指针碰撞和空闲列表。除了考虑如何划分可用空
间之外,还需要考虑在并发的情况卜的线程平安。解决方案有两种:一种是对分配空间的动作进行同步
处理;另外一种本地线程分配缓冲(TLAB)o
第三步,内存空间初始化。如果使用TLAB,这一过程可以提前至TLAB分配时进行。
第四步,必要的设置。初始化后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如
何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象
头中。
上面的工作完成后,从虚拟机角度来看,一个新的对象已经产生了,但在程序员的角度来看,对象的创
立才刚刚开始,init方法还没有执行,所有字段都还为零。所以,一般来说,执行new指令后会接着执
行init方法,把对象按照程序员的意愿进行初始化,这样一个可用的对象才算完全产生出来,
三对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局分为3块:对象头、实例数据和对齐填充。
对象头包括两局部信息,一局部用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标
志、线程持有的锁等;另■局部是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来
确定这个对象是哪个类的实例。
实例数据局部是对象真正存储佗有效信息,也是在程序代码中所定义的各种类型字段内容。无论是从父
类继承下来,还是在子类中定义的,都需要记录。这局部的存储顺序会受到虚拟机分配策略参数和字段
在Java源码中的顺序的影响。
对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotspotVM的自动内
存管理系统要求对象起始地址必须是8字节的整数倍。
四对象的访问定位
建立对象是为了使用对象,我优的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由
于reference类型在Java虚拟机标准里面只规定了一个指向对象的引用地址,并没有定义这个引用应该
通过那种方式去定位,访问到Java堆中的对象位置.,因此不同的虚拟机实现的访问方式可能不同,主流
的方式有两种:使用句柄和直接指针。
句柄访问方式:Java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而
句柄中包含了对象实例数据和类型数据各自的具体地址信息。
指针访问方式:reference变量中直接存储的就是对象的地址,而Java堆对象•局部存储了对象实例数
据,另外一局部存储了到对象类型数据的指针。
这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地
址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最
大好处就是速度快,它节省了一次指针定位的时间开销,就Hotspot虚拟机而言,它使用的是第二种方
式(直接指针访问)。
五JVM的内存配置参数
-XX:+<option>启用选项
・XX:Yoption>不启用选项
-XX:<option>=<value>将option参数的值设置为value
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小。通常为Xmx的1/3或1/4。新生代=Eden+2个Survivor空间。
实际可用空间为:Eden+1个Survivor,即90%o
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,
年轻代占整个年轻代年老代和的1"
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。
如:3,表示Eden:Survivor=3:2,一个Survivor■区占整个年轻代的1/5
-XX:PermSize=n永久代(方法区)的初始大小
-XX:MaxPermSize=n:设置永久代最大大小
-Xss设定栈容量;对于HotSpot来说,虽然・Xoss参数(设置本地方法栈大小)存在,但实
际上是无效的,因为在HotSpot中并不区分虚拟机和本地方法栈。
-XX:PretenureSizeThreshold(该设置只对Serial和ParNew收集器生效)可以设置进入老生
代的大小限制
-XX:MaxTenuringThreshold=n(默认15)垃圾最大年龄如果设置为0的话,那么年轻代对象
不经过Survivor区,直接进入年老代。对于年老代比拟多的应用,可以提高效率。如果将
此值设置为一个较大值,那么年轻代对象会在Survivor区进行屡次复制,这样可以增加对
象再年轻代的存活时间,增加在年轻代即被回收的概率该参数只有在串行GC时才有效。
收集器设置
・XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
・XX:+UseParal同OldGC:设置并行年老代收集器
・XX:+UseConcMarkSweepGC:设置并发q攵集器
垃圾回收统计信息
-XX:+PrintHeapAtGC打印GC的heap详情
-XX:+PrintGCDetails打印GC详情
-XX:+PrintGCTimeStamps打印GC时间信息
-XX:+PrintTenuringDistribution打印年龄信息等
-XX:+HandlePromotionFailure老年代分配担保(trueorfalse)
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数,
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
・XX:GCTimeRatio二n:设置垃圾回收时间占程序运行时间的百分比。公式为l/(l+n)
并发收集器设置
-XX:+CMSIncrementalMode:没置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。
并行收集线程数。
其他
-XX:PermSize=10Mff-XX:MaxPermSize=10M限制方法区大小。
-XX:MaxDirectMemorySize=10M指定DirectMemory(直接内存)容量,如果不指定,那么
默认与JAVA堆最大值(-Xmx指定)一样。
-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前
的内存堆转储快照(.hprof文件)以便时候进行分析(比方EclipseMemoryAnalysis)。
六JVM的堆内存(heap)
简单的来说Java的堆内存分为两块:permantspace(持久代/方法区)和heapspace。
持久代/方法区:主要存储结构信息的地方,比方方法体,同时也是存储静态变量,以及静态代码块的
区域,构造函数,常量池,接口初始化等等。与垃圾收集器要收集的Java对象关系不大。
而heapspace分为新生代和年老代。
新生代(由一个Eden区和俩个survivor区组成):对象被创立时(new)的对象通常被放在新生代的Eden
区(除了一些占据内存比拟大的对象直接进老年代),经过一次GC收集后,存活下来的会被复制到
定对象是否存活都与“引用”有关。引用关系:强引用〉软引月>弱引用〉虚引用0
垃圾收集算法
“标记-去除"(Mark-Sweep)算法。先标记后去除。缺乏:一,效率问题;二,空间问题,产生大量不
连续的内存碎片。
复制(Copying)算法。为了解决效率问题,出现了复制算法。将可用内存按容量划分为大小相等的两块,
每次只使用其中一块。当这一块的内存用完了,就将还存活的对象复制到另一块上,然后再把已使用过
的内存空间一次清理掉。实现简单,运行高效,但代价就是把内存缩小为原来的一半来使用,一般这种
收集算法主要用来回收新生代,因为新生代的对象绝大多数是'‘朝生夕死”的,存活时间短,所以并不
需要按1:1的比例来划分空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次
使用Eden空间和其中一块Survivor空间。当回收时,将Eden和Survivor中还存活的对象一次性地复制
到另一块Survivor空间上,最后清理掉Eden空间和刚刚用过的Survivor■空间°Hotspot虚拟机默认的Eden
和Survivor的大小比例是8:1。
“标记-整理“(Mark-Compact)克法。在老年代中因为对象存活率高、没有额外空间对它进行分配担保,
所以一般在老年代使用这种收集算法,,
分代收集算法。没什么新的思想,只是上面两种算法的在不同年代的使用。
垃圾收集器
l.SerialNew/SerialOld
Serial/SerialOld收集器是最根本最古老的收集器,它是一个单线程收集器,并且在它进行垃圾收集
时,必须暂停所有用户线程。SerialNew收集器是针对新生代的收集器,采用的是Spying算法,Serial
Old收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是实现简单高效,但是缺
点是会给用户带来停顿。
2.ParallelNew
ParallelNew收集器是Serial收集器的多线程版本(参照SerialNew),使用多个线程进行垃圾收集。除了
Serial收集器外,目前只有ParalelNew可以与CMS收集器配合工作。
3.ParallelScavenge
ParallelScavenge收集器是一个新生代的多线程收集器(并行收集器),它在I可收期间不需要暂停其
他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了到达一个可
控的吞吐量。
4.ParallelOld
ParallelOld是ParallelScavenge收集器的老年代版本(并行收集器。使用多线程和Mark-Compact
算法。
5.CMS
CMS(CurrentMarkSweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发
收集器,采用的是Mark-Sweep算法。CMS运行的过程:初始标记、并发标记、重新标记、并发去除。
6.G1
G1收集器是当今收集器技术开展最前沿的成果,它是一款面向效劳端应用的收集器,它能充分利
用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。G1的
运行过程:初始标记、并发标记、最终标记、筛选回收。
Hotspot虚拟机的垃圾收集器(两个收集器之间的连线,说明他们之间可以搭配使用)
虚拟机执行子系统
一类文件〔Class文件〕结构
实现语言与平台无关的根底是虚拟机和字节码存储格式。
Class文件是一组以8位(一个字节)为根底单位的二进制流,各数据项严格按顺序排列其中:中间没有
添加任何分隔符。根据Java虚拟机标准的规定,CLASS文件格式采用一种类似C语言结构体的伪结构来
存储,这种伪结构中只有两种数据类型:无符号数和表。
无符号数属于根本的数据类型,以ul,u2,u4,u8来分别表示一个字节,两个字节,四个字节和8个字节
的无符号数,无符号数用来描述数字,索引引用,数量值或按照UTF8编码构成字符串数。
表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表都习惯性的以“Jnfo”结尾,表用
于描述有层次关系的复合结构的数据。整个CLASS文件本质上也是一张表。
Class类文件格式按如下顺序排列:
类型名称数量
U4magic(魔数)1
u2minor_verson(次版本号)1
U2majojversion(主版本号)1
u2constant_pool_count(常量个数)1
cp_infoconstant_pool(常量池表)constant_pool_count-l
u2access_flags(类的访问控制权限)1
u2this_class(类名)1
u2superclass(父类名)1
u2interfaces_count(接口个数)1
u2interfaces(接口名)interfaces_count
u2fields_count(字段个数)1
field_infofields(字段表)fields_count
u2methods_count(方法的个数)1
method_infomethods(方法表)methods_count
u2attributes_count(属性的个数)1
attribute_infoattributes(属性表)attributes_count
魔数()(每个文件的头个字节):用来确定这个文件是否是文件,进
magicClass4OxCAFEBABEoClass
行身份识别。
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(2个字节),第7和
笫8个字节是主版本号(2个字节)。Java的版本号是从45(JDK1.1)开始,每个JDK版本发布主版本
号加1,高版本号的JDK可以向下兼容以前版本的Class文件,但不能运行版本高于自己的CLASS文件一
紧接着主次版本号之后的是常量池入口,常量池中常量的数量不同,用常量池计数器(2个字节)代表
常量池容量的计数值。计数器从1而不是0开始,例如当常量池容量为0x0016,十进制为22,代表有
21个常量。索引为1-21。没有使用0索引是因为在后面某些指向常量池的索引可以通过。索引表示不
引用任何一个常量池工程的意思。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(SymbolicReferences
字面量的例子有文本字符串,被声明为final的常量值等。
符号引用包含三类常量:类和接II的全限定名、字段的名称和描述符、方法的名称和描述符。
常量池后面紧接着是类的访问权限控制符:,类以及父类的全限定名,以及接口的个数,之后是接口的全
限定名,全限定名都是指向常量池的符号引用。
再下面就是字段表集合、方法表集合和属性表集合。
二虚拟机类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行验证、转换解析和初始化,最终形成可
以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
在Java语言里,类型的加载、连接(验证、准备、解析)和初始化的过程都是在程序运行期间完成的。
一个类的生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段.局部解析可以在初
始化开始之后再开始,这样可以支持java的运行时绑定。
Java虚拟机标准中并没有强制规定什么情况下需要开始类加载过程的第一个阶段:加载,这个交给虚拟
机自由把握,但却严格规定了有且只有5种情况必须立即对类进行初始化:
1)遇到new创立实例,getstatic获取类的静态字段,putstatic设置类的静态字段,invokestatic调用类的
静态方法
2)用包方法对类进行反射调用的时候,如果这个类没有初始化过,那么先触发其初始化
3)初始化一个类的时候,如果父类没有进行初始化,那么必须先触发其父类的初始化
4)当虚拟机启动的时候,需要指定一个执行的主类,虚拟时机先初始化这个主类
5)当使用JDK1.7的动态语言支持时,如果一个实例最后的解析结果REF_getStatic、REF_putStatic>
REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,那么需要先触发其初始化。
这5中场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方法都不会触发初始化,称
为被动引用。例如:①通过子类引用父类的静态字段,不会导致子类的初始化:②用new关键字创立数
组,通过数组定义来引用类,不会触发相应的类初始化:ACIass[]a=newACIass[10];
③调用一个类的静态常量也不会触发该类的初始化,因为调用类在编译阶段就已经把常量转化为对自己
的常量池的引用。
接口的初始化过程与类的初始化过程的显著区别:当一个类初始化时,要求其父类全部都已初始化过了,
但是一个接口在初始化时,并不要求其父接口全部都完成了初始化,只要在真正使用到父接口的时候(如
引用父接口中定义的常量)才会初始化。
三类加载的过程
类加载的过程:加载、验证、准备、解析和初始化。
加载阶段是整个类加载阶段的第一个阶段,在加载阶段主要完成3件事情:
1)通过类的全限定名来回去定义此类的二进制流
2)将这个二进制流所代表的静态存储结构转化为方法区的运行时数据结构
3)在java堆中生成一个代表这个类的对象,作为方法区这些数据的访问入口。
验证阶段,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚
拟机自身的平安。验证阶段大致上完成以下4种验证:Class文件格式的验证,元数据的验证,字节码的
验证,符号引用验证。
1)Class文件格式验证为了验证是否符合Class文件的格式,并且能被当前版本的虚拟机处埋。这个阶
段是验证是基于二进制字节流进行的,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中
在行存储,所以后面的3个验证阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流;
2)元数据验证是为了对类的元数据信息进行语义校验,保证不存在不符合java语义标准的元数据信息;
3)字节码验证主要是通过数据流和控制流,对类的方法体中的字节码进行校验分析;
4)符号引用验证主要是为了给解析阶段符号引用转化为直接引川做准备,对类自身以外的信息(常量
池中的各种符号引用)进行匹配性校验。
准备阶段,正式为类变量(被static修饰的变量)分配内存并设置初始值。这些变量使用的内存都将在
方法区中进行分配。说明两点:①这个阶段仅对类变量分配内存,不对实例变量分配,实例变量将会在
对象实例化时随着对象一起分配在Java堆中;②这里的初始值“通常情况”下是数据类型的零值。
“‘通常情况”:publicstaticinta=123;〃类变量在准备阶段初始化的值为0,而在初始化阶段,在《init>
构造方法中会把a的值初始化为123。
“不通常情况”:publicstaticfinalinta=123;〃用final修饰的类变量在准备阶段,会把a的值初始化为
123.,
解析阶段,把虚拟机在赏量池中的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、
类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用(后面3种是与JDK1.7新增的
动态语言支持有关
初始化阶段,是执行类构造器<cinit>()方法的过程,<cinit>()会自动收集类中的所有类变量以及静态语句
块(static。),在初始化《init>()方法的时候,虚拟时机自动调用父类的<cinit>()方法,接口的<cinit>()方
法可以到使用的时候在去初始化,虚拟时机保证《init>()方法在多线程环境先被正确的加锁和同步。还
有一个<init>()方法,这个方法是实例构造器[类的构造函数),在创立实例的时候会被调用并且初始化。
四Java类加载器(JavaClassLoader)
对于任意一个类,都需要根据加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性。每
一个类加载器,都拥有一个独立的类名称空间。通俗讲,判定两个类是否相等时,不仅要判断两个类名
是否相同,而且要判断是否由同一个类加载器加载的。
从Java虚拟机角度来讲,只存在两种不同的加载器:一种是启动类加载器(BootstrapClassLoader),这
个类加载器使用C++语言实现,是虚拟机的一局部;另一种就是所有其他的类加载器,这些类加载器都
由Java语言实现,独立于虚拟机外部,并且全部继承自抽象类。
从Java开发人员的角度来看,类加载器还可以划分得更细致一些,绝大局部Java程序都会变用一下3
种系统提供的类加载器:启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)、
应用程序类加载器(AppClassLoader)o
1)BootstrapClassLoader(引导类加载器)
负责把存放在$JAVA_HOME中jre/lib目录中的核心库和根底库加载到虚拟机内存中.由C++实现,不是
ClassLoader子类,无法被Java程序直接引用。
2)ExtensionClassLoader[扩展类加载器)
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext或指定目录下所有类库。开
发者可以直接使用扩展类加载器。
3)AppCIassLoade(应用程序类加载器)
负责加载用户类路径classpath中指定的类库。开发者可以直接使用扩展类加载器。
4)UserClassLoader(自定义类加载器)
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee标准自行实现
ClassLoader。
类加载器双亲委派模型
类加载器使用双亲委派模型,这样当要加载一个类时,首先查找这个类是否已经被加载过,如果没有,
那么类加载器会把这个类委派给这个加载器的父类去进行加载,如果父类不能加载,那么再自己尝试加
载。加载过程中会先检查类是否被已加载,检查顺序是自底向上,从CustomClassLoader到
BootStrapCIassLoader逐层检查,只要某个classloader已加载遂就视为已加载此类,保证此类只被所有
ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
ClassLoader加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的
时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外
一个classloader载入。所以,当我们自定义的classloader加载成功了以后,MyClass里所有依赖的class
都由这个classloader来加载完成。
五字节码执行
栈帧,是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区的虚拟机栈的栈
元素。栈帧中从上到下依次存储了方法的局部变量表,操作数栈,动态链接和方法返回地址等信息。每
个方法从调用开始到调用结束,都对应着一个栈帧从入栈到出栈的过程。一个栈帧需要分配多大的内存,
在编译程序代码期间就确定了。一个线程中只有栈顶的栈帧才是有效的,称为当前栈帧,这个栈帧所关
联的方法就是当前方法。
局部变量表,是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表的容
量以变量槽[Slot)为最小单位,为了节省栈帧空间,Slot是可以重用,但可能会影响到系统的垃圾收
集行为。在方法内的局部变量定义了但没有赋初始值是不能使用的。一般编译器会检查到并提示这一点。
操作数栈,也称为操作栈,是一个后人先出(UFO)栈。操作数栈中元素的数据类型必须要与字节码指
令的序列完全一致。
动态链接,是在运行期间把符号引用转化为直接引用的过程,相对于静态解析(在类加载过程的解析阶
段把符号引用转化为直接引用)。
方法的返回地址,方法返回有两种类型,一种是正常完成出口,另一种是异常完成出口,方法退出的过
程等同于栈帧出栈,因此栈帧出栈的时候可能执行的操作有:诙复上层调用方法的局部变量表和操作数
栈,把返回值压入调用方法的操作数栈中,调整PC计数器的值以执行方法调用指令的后一条指令等。
方法调用,不等同方法执行,唯一的任务就是确定被调用方法的版本,不涉及方法内部的具体运行过程。
Class文件的编译过程中不包含传统编译中的连接步骤,•切方法调用在Class文件里■面存储的都只是符
号引用,而不是方法在实际运行时内存入口地址(相当于之前说的直接引用)。
静态方法、实例构造器、私有方法、父类方法和final方法,这些方法叫做非虚方法。这类非虚方法的调
用叫做解析。解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及
的符号引用全部转变为可确定的直接引用,不会延迟到运行期间再去完成。
Humanman=newMan{);//Human是变量的静态类型Man是变量的实际类型
静态分派:所有依赖静态类型来定位方法执行版本的分派动作都称为静态分派,静态分派的典型应用就
是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
动态分派:在运行期根据实际类型确定方法的执行版本的分派过程称为动态分派,动态分派的典型应用
就是方法重写。
宗量:方法的接受者和方法的参数统称为方法的宗量”
单分派:根据一个宗量对目标方法进行选择
多分派:根据多个宗量对•目标方法进行选择
Java是一种静态多分派,动态单分派语言。
类的方法区会保存一张虚方法表,存放方法的实际入口地址,如果没有重写父类的方法,那么入口与父
类的一样,如果重写了父类的方法,那么方法的入口地址指向自己的方法入口地址。方法表一般在类加
载的连接阶段进行初始化,准备了类变量的初始值之后,虚拟时机把该类的方法表也初始化完毕,这是
java实现动态分派方法。
方法执行,Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行
[通过即时编译器产生本地代码执行)两种选择。基于栈的解释器执行
在Class文件格式与执行引擎这局部中,用户的程序能直接影晌的内容并不太多,Class文件以何种格式
存储,类型何时加载,如何连接,以及虚拟机如何执行字节码指令等都是由虚拟机直接控制的行为,用
户程序无法进行干预。能通过程序进行操作的,主要是字节码生成和类加载器这两局部,
程序编译
一编译期
Java语言的“编译期”其实是一段不确定的操作过程,因为它可能是指一个前端编译器把*Java文件转
变成*.class文件的过程;也可能是指虚拟机的后端运行期编译器把字节码转变成机器码的过程。Java的
即时编译器在运行期的优化过程对于程序运行来说更重要,而前端编译器在编译期的优化过程对F程序
编码来说关系更加密切。
Javac编译器,是Sun公司的前端编译期,它本身就是一个由Java语言编写的程序。从Javac的代码来看,
编译过程大致可以分为3个过程:解析与填充符号表过程、插入式注解处理器的注解处理过程和分析与
字节码生成过程。
Javac的编译过程
过程1.1:解析1词法、语法分析)
过程1.2:填充到符号表
过程2:执行注解处理
过程3.1:标注检查
过程3.2:数据及控制流分析
过程3.3:解语法糖(常见语法糖:泛型、自动装箱/拆箱、变长参数等)
过程3.4:字节码生成
二运行期
前面Javac这类将Java源代码转变成字节码的编译器一般称为“前端编译器”,是因为他只完成了从程
序到中间字节码的生成,而在此之后,还有一组在虚拟机内部的“后端编译器”完成了从字节码生成机
器码的过程,这类编译器一般称作为即时编译器或JIT编译器.工作在程序的运行期。这类编译器的编
译速度及编译结果的优劣,是衡量一个虚拟机性能很重要的指标。
Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码的运行特别频繁时,就会把
这些代码认定为“热点代码:为了提高热点代码的执行效率,在运行时,虚拟机将会把这些热点代码
编译成与本地平台相关的机器码,并进行各层次优化,完成这个任务的编译器就是即时编译器。(C/C++:
静态优化编译器)
Hotspot虚拟机采用的是解释器与编译器并存的架构v解释器和编济器两者各有优势,当程序需要快速
启动和执行的时候,解释器可以首先发挥作用,省去编译时间,立即执行。在程序运行后,随着时间的
推移,编译器逐渐发挥作用,把越来越多的代码编译本钱地代码后,可以获取更高的执行效率。
Hotspot虚拟机中内置了两个即时编译器,分别称为ClientCompiler和ServerCompiler(简称Cl和C2)。
用Cl可以获取更高的编译速度,用C2可以获取更好的编译质量。程序使用哪个编译器,取决虚拟机运
行模式,虚拟时机根据自身版本和宿主机器的硬件性能自动选择运行模式,用户也可以自己使用参数强
制指定运行模式。
在运行过程中能被即时编译器编译的“热点代码”有两类:①被屡次调用的方法;②被屡次执行的循环
体。
判断段代码是不是热点代码,需不需要触发即时编译,这样的行为称为热点探测。主耍的热点探测判
定方式:基于采样的热点探测和基于计数器的热点探测(Hotsoot)o
基于计数器的热点探测方式为每个方法准备了两个计数器:方法调用计数器(Client模式1500次,Server
模式10000次,计数热度衰减)和回边计数器(Client模式13995次,Server模式次700次,没有计数
热度衰减)。
编译优化技术:公共子表达式消除、数组范围检查消除、方法内联、逃逸分析
Javac字节码的编译器与虚拟机内的JIT编译器的执行过程合并起来其实就等同于•个传统编译器所执行
的编译过程。
高效并发
-Java内存模型
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变显存储到内存和从内
存中取出变量这样底层细节。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字
段和构成数组对象的元素,但是不包括局部变吊与方法参数,后者是线程私有的,不会被共享。
Java内存模型中规定了:所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内
存中保存了被该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值:都必须在
工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,
线程间变量值的传递均需要在主内存来完成。线程、主内存和工作内存的交互关系如下列图所示。
这里的主内存、工作内存与Java内存区域的Java堆、栈、方法区不是同一层次内存划分。如果非要对
应起来,主内存主要对应于Java堆中的对象实例数据局部,而工作内存那么对应于虚拟机栈中局部区域。
二内存间交互操作
关于主内存与工作内存之间的具体交互协议,即一个变最如何从主内存拷贝到工作内存、如何从工
作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
•lock[锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
•unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可
以被其他线程锁定。
•read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后
的load动作使用
•load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的
变量副本中。
•use[使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟
机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
•assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变
量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作.
•store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随
后的write的操作。
•write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存
的变量中。
如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变
量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述操作
必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插
入其他
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 电信网络技术运维工程师能力水平KPI考核表
- 团队合作的重要性:小学生团队协作的小学主题班会课件
- 合同解除谈判商洽函(6篇)范文
- 客户2026年8月账单支付催办函8篇范本
- 原料短缺应对策略通知(6篇)范文
- 小学主题班会课件环保知识大比拼
- 高炉炼铁工岗前实操熟练考核试卷含答案
- 都市农业项目社会经济效益评估表
- 2026湖南娄底涟源市市直事业单位公开选调工作人员21人考试备考试题及答案详解
- 紫胶制片工创新方法知识考核试卷含答案
- 皮带机运行维护培训课件
- 物业管理公司规章管理制度
- 等静压设备安全培训课件
- 2025年健康照护师高级考试题库及解析
- 《数字经济概论》(教案大纲)
- 2024年江苏科技大学辅导员考试真题
- DG∕TJ 08-2019-2019 膜结构检测标准
- 租赁仪器合同协议
- 成人原发性腹壁疝腹腔镜手术中国专家共识(2025版)解读课件
- 2024-2025学年河南省“金太阳联考”高一年级上学期期中考试数学试题(含答案)
- 2024年新北师大版八年级上册物理全册教学课件(新版教材)
评论
0/150
提交评论