JVM虚拟机内存模型实现规范:
按线程是否共享分为以下区域:
所有线程共享的数据区:
每个线程都会有一块私有的数据区:
以HotSpot虚拟机实现为例,Java8中内存区域如下:
与规范中的区别:
逃逸分析
逃逸是指在某个方法之内创建的对象除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收。由于其被其它变量引用,由于无法回收,即称为逃逸。
逃逸分析技术可以分析出某个对象是否永远只在某个方法、线程的范围内,并没有“逃逸”出这个范围,逃逸分析的一个结果就是对于某些未逃逸对象可以直接在栈上分配提高对象分配回收效率,对象占用的空间会随栈帧的出栈而销毁。
JVM在内存新生代Eden Space中开辟了一小块线程私有的区域TLAB(Thread-local allocation buffer)。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销
也就是说,Java中每个线程都会有自己的缓冲区称作TLAB,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可
初始化时机
加载过程
类加载器
启动类加载器:用C++语言实现,是虚拟机自身的一部分,它负责将
系统类加载器:用Java语言实现,它负责加载系统类路径ClassPath指定路径下的类库,开发者可以直接使用
双亲委派
定义:如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
优点:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次防止恶意覆盖Java核心API。
三次大型破坏双亲委派模式的事件:
新生代
进入条件
优先选择在新生代的Eden区被分配。
老年代
进入条件
回收对象
不可达对象:通过一系列的GC Roots的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时则此对象是不可用的。
GC Roots包括:虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象。
彻底死亡条件:
条件1:通过GC Roots作为起点的向下搜索形成引用链,没有搜到该对象,这是第一次标记。
条件2:在finalize方法中没有逃脱回收(将自身被其他对象引用),这是第一次标记的清理。
如何回收
新生代因为每次GC都有大批对象死去,只需要付出少量存活对象的复制成本且无碎片所以使用“复制算法”
老年代因为存活率高、没有分配担保空间,所以使用“标记-清理”或者“标记-整理”算法
复制算法:将可用内存按容量划分为Eden、from survivor、to survivor,分配的时候使用Eden和一个survivor,Minor GC后将存活的对象复制到另一个survivor,然后将原来已使用的内存一次清理掉。这样没有内存碎片。
标记-清除:首先标记出所有需要回收的对象,标记完成后统一回收被标记的对象。会产生大量碎片,导致无法分配大对象从而导致频繁GC。
标记-整理:首先标记出所有需要回收的对象,让所有存活的对象向一端移动。
Minor GC条件
当Eden区空间不足以继续分配对象,发起Minor GC。
Full GC条件
串行收集器Serial是最古老的收集器,只使用一个线程去回收,可能会产生较长的停顿
新生代使用Serial收集器复制
算法、老年代使用Serial Old标记-整理
算法
参数:-XX:+UseSerialGC
,默认开启-XX:+UseSerialOldGC
并行收集器Parallel关注可控的吞吐量,能精确地控制吞吐量与最大停顿时间是该收集器最大的特点,也是1.8的Server模式的默认收集器,使用多线程收集。ParNew垃圾收集器是Serial收集器的多线程版本。
新生代复制
算法、老年代标记-整理
算法
参数:-XX:+UseParallelGC
,默认开启-XX:+UseParallelOldGC
并发收集器CMS是以最短停顿时间为目标的收集器。G1关注能在大内存的前提下精确控制停顿时间且垃圾回收效率高。
CMS针对老年代,有初始标记、并发标记、重新标记、并发清除四个过程,标记阶段会Stop The World,使用标记-清除
算法,所以会产生内存碎片。
参数:-XX:+UseConcMarkSweepGC
,默认开启-XX:+UseParNewGC
G1将堆划分为多个大小固定的独立区域,根据每次允许的收集时间优先回收垃圾最多的区域,使用标记-整理
算法,是1.9的Server模式的默认收集器
参数:-XX:+UseG1GC
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互
STW总会发生,不管是新生代还是老年代,比如CMS在初始标记和重复标记阶段会停顿,G1在初始标记阶段也会停顿,所以并不是选择了一款停顿时间低的垃圾收集器就可以避免STW的,我们只能尽量去减少STW的时间。
那么为什么一定要STW?因为在定位堆中的对象时JVM会记录下对所有对象的引用,如果在定位对象过程中,有新的对象被分配或者刚记录下的对象突然变得无法访问,就会导致一些问题,比如部分对象无法被回收,更严重的是如果GC期间分配的一个GC Root对象引用了准备被回收的对象,那么该对象就会被错误地回收。
学习来源 https://github.com/xbox1994/2018-Java-Interview/blob/master/MD/Java%E5%9F%BA%E7%A1%80-JVM%E5%8E%9F%E7%90%86.md