JVM复习

JVM
介绍
jvm是程序虚拟机,运行字节码,主要功能是内存管理和垃圾回收。
目前主要使用的JVM为HotSpot,它采用解释器和即时编译器并存的架构
翻译字节码(解释执行):读一行执行一行,响应快,速度慢,类似走路
Jit编译器(翻译执行):将热点代码编译成机器码,并进行优化,下次执行就很快,响应慢,速度快,类似等公交车。
jvm采用的是基于栈的指令架构
基于栈的指令架构:零地址指令,完成一个功能需要更多指令,性能较低,不与硬件直接打交道,可移植,跨平台。
基于寄存器指令架构:多地址指令,完成一个功能花费更少指令,性能较高,指令集架构依赖硬件,可移植性差,设计实现也较复杂。
结构

类加载器
	定义
		类加载就是将磁盘上的class文件加载到内存中。类加载器记录了加载类的集合,就是哪些类是由它加载的。
	分类
		引导类加载器(BootstrapClassLoader):又称启动类加载器,使用c/c++实现,加载Java核心类库的类
		自定义类加载器:所有引导类加载器派生出的子类(主要包括:扩展类加载器,系统类加载器,用户自定义类加载器)
		用户自定义类加载器
			用途
				扩展加载源
				隔离加载类(如使用不同中间件时,防止加载的类重名冲突)
				防止源码篡改(对字节码加密,加载到内存时自定义类加载器解密)
				除了引导类加载器,其他加载器都不是必须的,可以使用自定义在需要的时候动态加载。
			实现方式
				继承ClassLoader或者URLClassLoader()
		继承关系
			引导类加载器->扩展类加载器->系统类加载器->用户自定义加载器
	类加载过程
		加载
			在内存中生成一个该类的Class对象(用于创建实例对象)
		链接
			验证:确保Class文件合法。
			准备:为类变量分配内存,设置默认初始值,如0,false…(类变量为static修饰的,这里不包含final static ,因为final static变量在编译时已经分配)
			解析:将常量池的符号引用转换为直接引用
		初始化
			执行类构造器方法 clinit() 的过程,该方法由编译器收集类中所有类变量赋值动作和静态代码块中的语句合并而来
	双亲委派机制
		原理
			类加载器收到加载请求后会把请求委托给父类加载器去加载,父类加载器不能加载,再由子类加载。
		加载过程
			启动类加载器看是否能加载这个类(在rt.jar中找是否有这个类,有就加载然后结束),不能加载就抛出异常,通知子类加载器加载,也就是看扩展类加载器是否能加载这个类(在ext目录下是否有这个类,有就加载结束),不能加载就继续通知子类加载器加载不断重复。
		优点
			避免类的重复加载
			防止核心API被篡改(沙箱安全机制)
运行时数据区
	相关
		jvm内存结构:程序计数器,方法区,本地方法栈,虚拟机栈,堆
		一个进程对应一个方法区和堆,一个线程对应一个程序计数器,虚拟机栈,本地方法栈。
		GC一般在方法区和堆中,OOM不会在程序计数器中
		OOM故障排除(调优)
			尝试扩大堆内存看看结果
			用内存快照分析工具分析内存(如Jprofiler,MAT)
				通过对程序设置参数(-XX:+HeapDumpOnOutOfMemoryError),当JVM发生OOM时,自动生成DUMP文件。用jprofiler打开,一是可以看哪些对象占了空间,二是可以通过查看各个线程看问题出在哪行
	程序计数器
		用于存储下一条执行指令的地址,多个线程情况下,当切换回某个线程的时候,就需要通过pc寄存器知道当前线程执行的位置。
	本地方法栈
		作用
			管理本地方法调用。
		本地方法
			带native关键字的方法,主要由c编写,本地方法可以直接调用处理器中的寄存器,分配本地内存等。
	虚拟机栈
		作用
			参与方法调用和返回,保存局部变量和部分结果。扩展:每个线程创建时会创建一个虚拟机栈,虚拟机栈中保存的是多个栈帧(内存区块),一个栈帧对应一个方法,虚拟机栈大小可以固定或者动态变化。(通过-Xss 设置)
		栈帧组成
			局部变量表
				它是一个数子类型的数组(定义的所有类型都能转换为数字),其存储单元为变量槽(Slot),32位以内类型变量占一个槽)若变量作用域失效,变量槽便可以回收重用。局部变量表中直接或间接引用的对象不会被回收,随方法调用结束销毁。局部变量表是性能调优的重要部分。
			操作数栈
				用于保存计算的中间结果(比如下一条字节码为加操作,通过局部变量表是不知道哪两个数相加。但通过操作数栈弹出两个栈顶数就可以。)
			动态链接
				作用
					每个栈帧都保存了 一个 可以指向当前方法所在类的 运行时常量池, 作用就是: 当前方法中如果需要调用其他方法的时候, 通过运行时常量池,就可以将符号引用转换为直接引用,然后就能直接调用对应方法。
				常量池
					常量池:存放final常量,字符串和基本数据类型的值,符号引用(方法的名称和描述符,字段的名称和描述符)。它是字节码文件中的一部分 ,本质就是符号地址和真实地址的对照表。
					运行时常量池:当类的字节码被加载到内存中后,它的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址变为真实地址(如#2会被转化为内存中的地址)。位置:1.6之前在方法区,1.7在堆,1.8后在元空间
				两类方法调用
					静态链接:目标方法在编译期可知(对应方法早期绑定非虚方法)
					动态链接:目标方法在运行时才可知(对应方法晚期绑定和虚方法)比如多态中,animal类可能时狗猫,所以编译时不知道调用狗的eat()还是猫的eat();所以为晚期绑定。
					不同方法调用指令不同,java7出现动态调用指令invokedynamic。它是为了实现 动态类型语言支持而做的一种改进。(静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息)
					虚方法表
						调用虚方法时,在运行过程中根据调用者的动态类型来决定具体的目标方法(多态),效率就很低,所以引入虚方法表。每个类中都有一个虚方法表,存储各方法实际入口
			方法返回地址
				用于存放pc寄存器的值 ,在该方法结束后返回到被调用的位置。如果正常退出就到调用位置的下一条语句,异常退出根据异常表确定(比如try catch捕获了就会接着执行,未捕获就会向上抛 )
			一些附加信息(了解)
				栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如对程序调试提供支持的信息。
	方法区
		存放已被加载的类信息(构造方法/接口定义)、常量、静态变量。(一个java进程对应一个方法区,1.8以前又叫永久代)
		元空间:1.8后方法区被元空间替代,元空间使用本地内存。(不存在垃圾回收,如果加载太多的第三方jar包,可能导致内存溢出)
	堆
		介绍
			1. 一个java进程对应一个堆内存,堆内存大小在创建时确定(可设置)堆可以在物理上不连续,但必须在逻辑上连续。2. 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
		结构
			新生代:伊甸园区和幸存区(from 和 to)+老年代(伊甸园和from,to比例8:1:1,新生代老年代比例1:2)
		TLAB(ThreadLocal)
			定义
				从堆的新生代区划分出一部分空间,为每个线程分配一个私有缓存区。这个缓存区被称为TLAB(Thread Local Allocation Buffer)。
			优点
				因为共享区的数据为了安全会使用加锁等机制,从而影响效率,所以产生TLAB,当线程数据在私有缓存区放不下才放到共享数据区。
		逃逸分析
			定义
				逃逸主要是指new的对象是否在方法外使用,如果在方法外使用了就是发生了逃逸,如果没发生逃逸就可以做一些优化。如:(java7之后,默认开启了逃逸分析,java7之前需要设置参数开启。)注意:只有在server模式下,才可以开启逃逸分析。
			开启后编译器可对代码优化
				栈上分配对象
					除了堆中分配对象外,在栈上也可以分配对象,不过有条件,经过逃逸分析后发现一个对象如果没有发生逃逸,就可以在栈上分配,如果发生逃逸就不能栈上分配。(栈帧弹出栈,变量就自动被回收,就不用GC。)
				同步省略(锁消除)
					JIT编译器可以借助逃逸分析来判断一个对象是否只被一个线程访问,如果是,JIT在编译这个同步块的时候,会取消这部分代码的同步,这样能提高并发性和性能
				分离对象(标量替换)
					在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个其中包含的若干个成员变量来代替。这个过程就是标量替换。(无法分解成更小数据的数据,如java的基本数据类型)
		堆设置参数
			-Xms:设置堆初始内存大小(默认电脑内存大小1/64)
			-Xmx:设置堆的最大内存大小(默认电脑内存大小1/4)
			-Xmn:设置新生代大小(可设置初始值和最大值)
			-XX:NewRatio=2:设置新生代和老年代占比为1:2(默认就是2)
			-XX:ServivorRatio=8:设置伊甸园区和幸存区比例为8:1:1(默认就是8)
			-XXHandlePromotionFailure:设置空间分配担保(1.7之前有效,1.7之后固定为true)
垃圾回收
	新对象申请内存过程
		1. 先判断伊甸园是否能放下,2. 不能就YGC然后判断伊甸园是否能放,3. 还不能就判断是否Old区能否放下, 4. 还不能就FGC然后判断是否能放下,不能就OOM。(能就分配内存)
	YGC
		新生代范围的gc。伊甸园区空间不足时触发,频率比较高(常用复制算法)
		YGC前(空间分配担保):步骤:1. 老年代最大连续可用空间是否大于新生代所有对象的总空间,是的话YGC,2. 不是则判断老年代最大连续可用空间是否大于之前晋升的平均大小,是的话YGC,否则FGC。(jdk1.7前:在2步骤前,如果参数HandlePromoionFailure为true才进行2步骤,为false就直接FGC)
	FullGC
		FGC:全堆范围的gc,老年代空间不足或者对空间使用达到80%触发(可调整)或者调用System.gc() (常用分代收集算法,新生代和老年代采取不同收集算法)
	常用GC算法
		复制算法
			每次ygc,将伊甸园区活下来的对象和from区寿命未超阈值的对象复制到to区,然后交换from和to。(每次from区对象复制到to对象寿命+1,超阈值15的放old区。如果to区放不下也放old区)
			优点:没有碎片空间,缺点浪费空间,因为始终有一个幸存区为空。适用对象存活度较低的场景
		标记清除法
			扫描两次,第一次标记要清除的对象,第二次清除标记的对象
			优点不需要额外空间,缺点产生内存碎片
		标记压缩法(标记整理算法)
			三次扫描,在标记清除法的基础上,第三次是向一端移动对象,消除内存碎片
			优点无内存碎片,缺点三次扫描效率低
		标记清除压缩
			与标记压缩法不同在于多进行几次标记清除,产生较多的碎片后再进行压缩
	垃圾回收器
		新生代
			Serial: 单线程收集器,采用复制算法,优点:对垃圾回收来说简单高效。缺点:会造成STW(就是垃圾回收时用户线程停止))
			ParNew: 多线程收集器,相对Serial是多线程去执行垃圾回收。(只是多线程进行垃圾回收,也会造成STW)
			Parallel Scavenge: 多线程收集器,相对ParNew,可实现用户线程可控的吞吐量,就是可以设置吞吐量大小和gc停顿时间(吞吐量:CPU运行用户程序时间除以CPU运行总时间)。
				具体:有个参数(UseAdaptiveSizePolicy)打开之后,就不需要手动指定新生代大小、Eden区和Survivor参数等细节参数了,虚拟机会根据当前系统的运行情况,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
		老年代
			Serial Old
				Serial收集器的老年代版本,单线程收集器,采用标记压缩算法 。
			Parallel Old
				Parallel Scavenge 的老年代版本,多线程收集器,采用标记压缩算法,能实现可控吞吐量 。
			CMS
				并发收集器,垃圾回收线程和用户线程同时执行(在并发标记阶段不会造成STW),采用标记清除算法。
				优缺:优点:低停顿。缺点:1. 无法清除浮动垃圾(主要在并发清除阶段,因为并行,所以在回收过程,用户程序还会产生垃圾)。2. 因为采用标记清除法,所以会产生内存碎片。3. 对cpu敏感,CMS默认启动的回收线程数是(CPU数量+3)/ 4,如果cpu数量越少,处理用户线程的cpu资源就越少
				过程
					初始标记:标记 GCRoots 能直接关联到的对象,速度很快(为了防止程序继续产生GC Root对象,所以需要STW)
					并发标记:基于初始标记的GC Root对象,进行可达性分析,标记存活对象,它在整个回收过程中耗时最长。(不会造成STW)
					重新标记:并发标记期间,因用户程序的运行产生一些新的垃圾,很少所以快(为了防止继续产生垃圾所以需要STW)。
					并发清除:并发的清除没有标记的垃圾对象。(不会造成STW)
		新一代收集器G1
			采用标记压缩算法,不区分新生代和老年代,直接把堆空间划分固定大小的region,在后台维护一个set集合指向这些region,根据筛选决定回收哪些region的垃圾(回收运行的时间、垃圾堆积程度以及region的优先级)。
			优缺:优点:不会产生内存碎片,能精确控制垃圾回收时间。缺点:维护指向每个region区域的指针,占用额外内存。
			过程
				初始标记:标记 GCRoots 能直接关联到的对象,速度很快(为了防止程序继续产生GC Root对象,所以需要STW)
				并发标记:基于初始标记的GC Root对象,进行可达性分析,找到存活对象,它在整个回收过程中耗时最长。(不会造成STW)
				最终标记:并发标记期间,因用户程序的运行产生一些新的垃圾,很少所以快(为了防止继续产生垃圾所以需要STW)。
				筛选回收:根据回收运行的时间、垃圾堆积程度以及region的优先级来回收对应region。(会造成STW)
		相关问题
			新生代为什么用复制算法:因为复制只需要一次扫描,效率高。新生代存活对象少,所以复制所需的额外空间小。
			G1和CMS对比:1. G1采用标记压缩不会产生内存碎片。2. G1具有筛选回收,所以对回收时间可预测(也可自己设置)3. G1在筛选回收时候不是并行所以不会产生浮动垃圾,CMS是并发清除会产生浮动垃圾。4. G1内存占用更多(因为后台需要维护指向每个区域的Set集合)
			GCRoot对象
				比如方法中定义了一个对象,那个对象就是GCRoot对象。主要用于判断哪些对象可以被回收:通过可达性分析:通过GC Root对象向下搜索(G Croot对象中可能引用了其他对象),形成的路径为引用链。对访问过的对象打上标记,没有标记的对象等待被回收。
面试题
	内存溢出举例?
		JVM栈溢出:局部数组过大,递归调用层数太多,大量循环
		JVM堆溢出:创建对象太多
	为什么CMS会将serial old作为后备垃圾回收器?
		因为CMS采用标记清除算法,会产生内存碎片。serial old采用标记压缩算法,能避免产生内存碎片
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值