JVM的整体结构
简图:
详细图:
运行时数据区结构图
程序计数器
当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,由存储引擎读取下一条指令;生命周期与线程的生命周期保持一致线程私有。
- 它是程序控制流的指示器,分支、循环、跳转、和异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令。
- 它是唯一一个在Java虚拟机规范中没有规定任何OutMemoryError情况的区域。
Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack), 早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存着一个个栈帧(Stack Frame),对应着一次次Java方法的调用;生命周期和线程一致,是线程私有的。
栈是运行时的单位,而堆是存储的单位。
即:栈解决程序的运行问题,程序如何执行,或者如何处理数据;堆解决的是数据存储的问题,数据怎么放,放在哪儿。
本地方法栈
Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈也是线程私有。
- 可动态扩展的内存大小。
- 本地方法使用C语言实现的。
- 它的具体做法是Native method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
堆(Heap)
- 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。
- Java虚拟机堆区在JVM启动时即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
- 堆内存大小是可调节的。
- 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应被视为连续的。
- 所有的线程共享堆区,在这里还可以划分线程私有缓冲区(Thread Local Allocation Buffer, TLAB)。
- 《Java虚拟机规范》中对Java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。
- 数组和对象永远不会存储在栈上,因为栈帧保存引用,这个引用指向对象或数组在队中的位置。
- 在方法结束后,堆中的对象不会马上移除,仅仅在垃圾收集的时候才会被移除。
- 堆,是GC(Garbage Collection) 执行垃圾回收的重点区域。
现代垃圾收集器大部分都基于分代收集理论设计,java8及之后的堆内存逻辑上分为三部分:新生区+养老区+元空间。
新生代对象分配与回收过程
为新生代对象分配内存是一件非常严谨和复杂的任务,JVM的设计者们不仅要考虑内存如何分配,在哪里分配等问题,并且由于内存分配算法与内存回收算法密切相关,所以还要考虑GC执行完内存回收后是否会在内存中产生内存碎片。
- new的对象首先放到Eden区,此区有大小限制。
- 当Eden的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对Eden区进行垃圾回收(Minor GC),将Eden区中不再被其它对象所引用的对象进行销毁,再加载新的对象到Eden区。
- 再将Eden区剩余的对象移到Survivor0区。
- 如果再次出发垃圾回收,此时此时上次幸存下来的放到Survivor0区的对象,如果没有回收,就会放到Survive1区。
- 如果再次经理垃圾回收,此时会重新放会Survivor0区,接着再去幸存者1区。
- 什么时候这些常时间没有被回收的对象会进入到老年区域呢?可以设置次数。默认是15次。
Minor GC、MajorGC、Full GC的对比
JVM在进行GC时,并非每次都对新生代、老年代、方法区一起回收,大部分的回收的都时指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大类:部分回收(Partial GC) 和 整堆回收(Full GC)
-
部分收集:不是完整收集整个Java堆的垃圾收集。其中有分为:
新生代收集(Minor GC / Yong GC): 只是新生代的垃圾收集。
老年代收集(Major GC / Old GC): 只是老年代垃圾收集。
混合收集(Mixed GC): 收集整个新生代以及部分老年代的垃圾收集。 -
整堆收集(Full GC): 收集整个java堆和方法区的垃圾收集。
内存分配策略
针对不同阶段的对象分配原则如下所示:
- 优先分配到Eden
- 大对象直接分配到老年代
- 长期存活的对象分配到老年代
- 动态对象年龄判断:如果survivor区中相同年龄的所有对象大小总和大于survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入到老年代,无需等到MaxTenuringThreshold中要求的阈值。
对象分配过程 TLAB
为什么会有TLAB(Thread Loacl Allocation Buffer)
- 堆区是线程共享区域,任何线程都可以访问到堆区的共享数据
- 由于对象实例在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的。
- 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度
什么是TLAB
- 从内存模型而不是垃圾回收的角度,对Eden区进行进一步划分,JVM为每个线程分配了一个私有的缓存区域,它包含在Eden区。
- 多线程同时分配内存时,使用TLAB可以避免一系列线程安全问题,同时还能提升内存的分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略。
方法区
栈、堆、方法区(元空间)的交互关系
方法区存储的是什么
《深入理解Java 虚拟机》中对方法区存储的内容描述如下:它用于存储已经被虚拟机加载的类型信息、常量、静态变量、域(Fields)、即时编译后的代码缓存等等。通过编译后在字节码文件中的常量池通过类加载器加载到内存方法区后就是运行时常量池,其中常量池可以看做是一张表,虚拟机指令可以根据这张常量表找到要执行的类名,方法名、参数类型字面量等类型(特别说明:字符串常量和静态变量仍然在堆里面)。
方法区的垃圾回收主要收集两部分内容:常量池中废弃的常量和不再使用的类型。
垃圾回收相关算法
- 垃圾标记阶段的算法之:引用计数算法
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要A的引用计数器为0,即表示对象A不可能被使用,可进行回收。
有点:实现简单,判定效率高。
缺点:无法处理循环引用情况,这是一条致命的缺陷,导致java垃圾回收器中没有使用这类算法。 - 垃圾标记阶段的算法之:可达性分析算法
相对于引用计数算法,可达性分析算法不仅同样具备实现简单和高效等特点,更重要的时该算法可以有效的解决循环引用的问题,防止内存泄漏的发生;此算法时Java与C#选择的,这种类型的垃圾收集通常也叫追踪性收集。
评估GC的性能指标:吞吐量
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即:
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。
垃圾回收器概述
7款经典的垃圾回收器
- 串行回收器:Serial、Serial Old
- 并行回收器:ParNew、Parallel ScaVenge、Parallel Old
- 并发回收器:CMS、G1
7款经典收集器与垃圾分代之间的关系
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS(Concurrent Mark Sweep)
- 整堆收集:G1
垃圾收集器的组合关系
Serial回收器:串行回收
- 这种回收器只做了解,现在已经不用串行的了,而且限定单核的CPU才可以用。现在都不是单核的了。
- 对于交互较强的语言,这种垃圾收集器是不能接受的。一般在Java web应用程序中是不会采用串行垃圾收集器。
ParNew回收器:并行回收
- ParNew收集器是Serial的多线程版本
- Par是Parallel的缩写,New说明只能处理新生代
- ParNew收集器在年轻代同样采用的是复制算法、Stop-the-world 机制
- ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器。
Parallel Scavenge回收器:吞吐量优先
- Hotspot的年轻代除了拥有ParNew收集器是基于并行回收的意外,Parallel Scanvenge 收集器同样也采用了复制算法、并行回收和Stop-the-world机制
- 与ParNew不同,Parallel Scavenge收集器的目标则是达到一个可控的的吞吐量,它也被称为吞吐量优先的垃圾收集器。
- 自适应调节策略也是Parallel Scavenge与Par New一个重要的区别
- Parallel Old在用来代替老年代的Serial Old收集器;Parallel Old 收集器采用了标记压缩算法,同样也是基于并行回收和Stop-the-world机制
- JDK8中的默认垃圾收集器
CMS垃圾回收期:低延迟
- 在JDK1.5时期,Hotspot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent Mark Sweep),这款收集器是Hotspot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。
- CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间
- CMS垃圾收集器采用标记-清除算法,并且也会Stop-the-world
- 在G1出现之前,CMS使用还是非常广泛的。
- 弊端:会产生内存碎片
G1回收器:区域化分代式(重点)
- JDK9之后的默认垃圾回收器
- 在JDK1.8中要使用G1,需要配置-XX:UseG1GC来启用
- G1是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大量内存的机器,以极高概率满足GC停顿的时间,还兼顾高吞吐量的性能特征。