概念:JVM是可运行的java代码的虚拟计算机,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。

JDK、JRE和JVM的区别
JDK包含JRE,JRE包含JVM;JVM就是java虚拟机。
JVM运行时数据区
JDK1.8之前

JDK1.8

线程私有的
程序计数器、虚拟机栈、本地方法栈;
线程共享的
堆、方法区、直接内存(非运行时数据区的一部分)
方法区
- 方法区是所有线程共享的内存区域,用于存储已被java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
堆内存
- 堆是java虚拟机锁管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
- 在java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配;
- java堆是垃圾回收机器管理的主要区域;
- 从内存回收角度看java堆可分为老年代和新生代;
- 从内存分配的角度看,线程共享的堆中可以划分出多个线程私有的分配缓冲区;
程序技术器
- 程序计数器是一块较小的内存空间,可以看作是保存当前线程执行的行号;
- 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
虚拟机栈
- 虚拟机栈是线程私有的,生命周期和线程相同;
- 虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 栈帧:虚拟机中的单位就是栈帧,一个方法一个栈帧;

1. 局部变量表:是用来存储链式的基本数据类型、引用地址、返回类型;
2. 操作数栈:操作数栈就是用来操作的,例如代码中的运算,在一开始的时候就进行操作,读取代码,然后计算后放入局部变量表中;
3. 动态链接:即在方法中,调用别的接口,链接到别的方法中;
4. 方法出口:return或者异常;
思考:
一个方法调用别的方法会创建栈帧吗?
会创建,如果方法调用别的方法,则会创建新的栈帧,栈中是由顺序创建栈帧的。
栈指向堆是什么意思?
递归的调用自己会创建很多栈帧吗?
递归的话也会创建多个栈帧,就是一直排下去;
本地方法栈
本地方法栈跟虚拟机栈的功能类似,虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。这里的「本地方法」指的是「非Java方法」,一般本地方法是使用C语言实现的。
直接内存
- 直接内存不是虚拟机运行的一部分,也不是虚拟机规范中定义的内存区域,但是也被频繁调用,可能导致oom;
- 本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存⼤⼩以及处理器寻址空间的限制。
直接内存和堆内存的区别
- 直接内存申请空间耗费很高的性能,堆内存申请空间耗费比较低;
- 直接内存的IO读写性能要优于堆内存;
垃圾回收机制(GC)
用于java堆的管理,堆是java所管理的最大的一块内存空间,主要用于存放各种对象的实例;
什么是gc
程序在运行过程中,会产生带量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
GC是不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行清除某个对象。通过调用System.gc 方法来”建议”执行垃圾收集器,但是他是否执行,什么时候执行却都是不可知的。
finalize方法的作用
- finalize方法时在每次执行GC操作之前会调用的方法,可以用来做必要的清理工作;
- 在Object类中定义,因此所有的类都集成了它,子类覆盖finalize方法以整理系统资源或者执行其他清理工作;
- 在Object中finalize方法是个空的方法;
新生代、老年代、永久代(方法区的区别)
- 老年代就一个区域;新生代分为,Eden、Form Survivor、To Survivor;
- 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
- 默认的,Edem : From Survivor : To Survivor = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,From Survivor = To Survivor = 1/10 的新生代空间大小。
- JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
- 永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
为什么这样分代
- 新生代中,每次垃圾回收都会有大量的对象死去,只有少量对象存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;
- 老年代中因为对象存活率高,没有额外的空间进行分配担保,必须采用”标记-清理”或”标记-整理”算法;
- 新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。加上老年代就这三个区。数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。
Minor GC、Major GC、Full GC区别及触发条件
- Minor GC:新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快;
触发条件:eden区满的时候触发一次;新创建的对象大于eden区所剩空间时;
- Major GC:老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多;
- Full GC是清理整个堆空间,包括年轻代和老年代;
触发条件:Major GC和Full GC
1. 每次晋升到老年代的对象平均大小>老年代剩余空间;
2. Minor GC后存活的对象超过了老年代剩余空间;
3. 永久代空间不足;
4. 执行System.gc方法;
5. CMS GC异常;
6. 堆内存分配很大的对象;
如何判断对象是否存活
引用计数器
- 引用计数法就是如果一个对象没有被任何引用指向,则可视为垃圾;
- 主流的Java虚拟机里面都没有选用引用计数算法来管理内存;
- 引用计数法:每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,计数器为0就代表该对象死亡;
引用计数器的优点
实现简单,判断效率高。
引用计数器的缺点
主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题;不建议使用。
public class Test {
public Object object = null;
public static void main(String[] args) {
Test a = new Test();
Test b = new Test();
/**
* 循环引用,此时引用计数器法失效
*/
a.object = b;
b.object = a;
a = null;
b = null;
}
}可达性分析法
- 该方法是从GC Roots开始向下搜索,搜索走过的路径为引用链。当一个对象到GC Roots没有任何引用链,则证明此对象是不可用的,可以回收;

- 上图中obj1、obj2、obj3、obj4、obj5到GC Roots是可达的,表示它们是有引用的对象,是存活的对象不可以进行回收;
- obj6、obj7、obj8虽然是互相关联,但是他们到GC Roots是不可达的,所以表示可以进行回收的对象;
可以作为GC Roots的对象
1.虚拟机栈(栈帧中的本地变量表)中引用的对象;
2.方法区中静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中Native方法引用的对象等。可达性算法的优缺点
- 优点:解决互相循环引用问题;
- 缺点:目前和引用计数法比没有缺点;
可达性算法的应用场景
目前主流虚拟机都是采用可达性分析法。
垃圾回收机制策略(GC算法)
引用计数算法
标记-清除算法(Mark-Sweep)
为每个对象存储一个标记位,记录对象的状态(存活或者死亡);
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二阶段是清除阶段,该阶段对死亡的对象进行清除,执行GC操作;
优缺点
- 优点
- 是可以解决循环引用的问题;
- 必要时才回收(内存不足时)
- 缺点
- 回收时,应用需要挂起,也就是stop the world;
- 标记和清除的效率不高,尤其是要扫描的对象比较多的时候;
- 会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到);
应用场景
应用于老年代,因为老年代的对象生命周期比较长。
标记-整理算法
标记清除算法和标记压缩算法非常相同,但是标记压缩算法在标记清除算法之上解决内存碎片化(有些人叫”标记整理算法”为”标记压缩算法”);
标记-整理法是标记-清除法的一个改进版。在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。这样就达到了标记-整理的目的。
优缺点
- 优点:解决标记清除算法出现的内存碎片问题;
- 缺点:压缩阶段,由于移动了可用对象,需要去更新引用中存储的地址。
应用场景
应用于老年代。
复制算法
该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。
这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。
优缺点
- 优点:
在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题。
- 缺点:
会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制算法的性能会变得很差。
应用场景
- 复制算法一般使用在新生代,因为新生代的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用复制算法进行拷贝时效率比较高。
- jvm将Heap(堆)内存划分为新生代与老年代。又将新生代划分为Eden与2块Survivor Space(幸存者区) ,然后在Eden –>Survivor Space 与To Survivor之间实行复制算法。
- 不过jvm在应用复制算法时,并不是把内存按照1:1来划分的,这样太浪费内存空间了。一般的jvm都是8:1。也即是说,Eden区:From区:To区域的比例是始终有90%的空间是可以用来创建对象的,而剩下的10%用来存放回收后存活的对象。
垃圾收集器
什么是垃圾收集器
- 垃圾回收器是垃圾回收算法(引用计数法、标记清除法、标记整理法、复制算法)的具体实现,不同垃圾收集器、不同版本的JVM提供的垃圾收集器可能会有差别。
- JDK8中包含Serial、ParNew、Parallel Scavenge、CMS、Serial Old、Parallel Old、G1;
按作用区域进行分类
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器:G1
垃圾回收器详解
| 垃圾回收器 | 工作区域 | 回收算法 | 工作线程 | 用户线程并行 | 描述 |
|---|---|---|---|---|---|
| Serial | 新生代 | 复制算法 | 单线程 | 否 | Client模式下默认新生代收集器。简单高效 |
| ParNew | 新生代 | 复制算法 | 多线程 | 否 | Serial的多线程版本,Server模式下首选, 可搭配CMS的新生代收集器 |
| Parallel Scavenge | 新生代 | 复制算法 | 多线程 | 否 | 目标是达到可控制的吞吐量 |
| Serial Old | 老年代 | 标记-整理 | 单线程 | 否 | Serial老年代版本,给Client模式下的虚拟机使用 |
| Parallel Old | 老年代 | 标记-整理 | 多线程 | 否 | Parallel Scavenge老年代版本,吞吐量优先 |
| CMS | 老年代 | 标记-清除 | 多线程 | 否 | 追求最短回收停顿时间 |
| G1 | 新生代+老年代 | 标记-整理+复制算法 | 多线程 | 否 | JDK1.9默认垃圾收集器 |
Serial
- 新生代收集器,使用复制算法收集新生代垃圾;
- 单线程的收集器,GC工作时,其他线程都停止工作;
- 简单高效,适合单CPU环境,单线程没有线程交互的开销,因此拥有最高的单线程收集效率。
使用方式
设置垃圾收集器:“-XX:+UseSerialGC” —添加该参数来显式的使用改垃圾收集器;
ParNew
- 新生代收集器。ParNew垃圾收集器是Serial收集器的多线程版本,采用复制算法;
- 除了多线程其他行为特点和Serial收集器一样;
- 只有它能与CMS收集器配合使用;
- 在单个CPU环境,不必Serial收集器好。
使用方式
设置垃圾收集器:“-XX:+UseParNewGC” —强制指定使用ParNew;
设置垃圾收集器: “-XX:+UseConcMarkSweepGC” —指定使用CMS后,会默认使用ParNew作为新生代收集器;
设置垃圾收集器参数:“-XX:ParallelGCThreads” —指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
Parallel Scavenge
- 新生代收集器;
- 采用复制算法;
- 多线程收集;
- 与ParNew不同的是:高吞吐量为目标,减少垃圾回收时间。
使用方式
设置垃圾收集器:“-XX:+UseParallelGC” —添加该参数来显式的使用改垃圾收集器;
设置垃圾收集器参数:“-XX:MaxGCPauseMillis” —控制垃圾回收时最大的停顿时间(单位ms)
设置垃圾收集器参数:“-XX:GCTimeRatio” —控制程序运行的吞吐量大小吞吐量大小=代码执行时间/(代码执行时间+gc回收的时间)
设置垃圾收集器参数:“-XX:UseAdaptiveSizePolicy” —内存调优交给虚拟机管理
Serial Old
- 老年代回收器,采用标记-整理算法;
- 单线程收集器
使用方式
在JDK1.5及之前,与Parallel Scavenge收集器搭配使用,
在JDK1.6后有Parallel Old收集器可搭配。
现在的作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
Parallnel Old
- 针对老年代,采用标记-整理算法;
- 多线程收集;
- 单个CPU环境中,不如Serial Old收集器好
使用方式
设置垃圾收集器:“-XX:+UseParallelOldGC”:指定使用Parallel Old收集器;
CMS
- 针对老年代,采用标记-清除算法会产生内存碎片;
- 以获取最短回收停顿时间为目标;
- 并发收集,低停顿;
- CMS收集器有三个缺点:对CPU资源敏感;无法处理浮动垃圾;产生大量内存碎片;
- 垃圾收集线程与用户线程可以同时工作
使用方式
设置垃圾收集器:“-XX:+UseConcMarkSweepGC”:指定使用CMS收集器;
G1
- 分代收集器,采用标记-整理+复制算法回收;
- 管理整个堆,不需要其他收集器配合;
- 并发让回收线程和用户线程同时进行
使用方式
设置垃圾收集器:“-XX:+UseG1GC”:指定使用G1收集器;
设置垃圾收集器参数:“-XX:InitiatingHeapOccupancyPercent”:当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45;
设置垃圾收集器参数:“-XX:MaxGCPauseMillis”:为G1设置暂停时间目标,默认值为200毫秒;
设置垃圾收集器参数:“-XX:G1HeapRegionSize”:设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region
JVM参数配置
简述
#常用的设置
-Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
-Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。
-Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。
-XX:NewSize=n 设置年轻代初始化大小大小
-XX:MaxNewSize=n 设置年轻代最大值
-XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4
-XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
-Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
-XX:ThreadStackSize=n 线程堆栈大小
-XX:PermSize=n 设置持久代初始值
-XX:MaxPermSize=n 设置持久代大小
-XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。
#下面是一些不常用的
-XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
-XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
-XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用
-XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
-XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用
-Xnoclassgc 是否禁用垃圾回收
-XX:+UseThreadPriorities 使用本地线程的优先级,默认启用
等等等......GC收集器设置
-XX:+UseSerialGC:设置串行收集器,年轻带收集器
-XX:+UseParNewGC:设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值。
-XX:+UseParallelGC:设置并行收集器,目标是目标是达到可控制的吞吐量
-XX:+UseParallelOldGC:设置并行年老代收集器,JDK6.0 支持对年老代并行收集。
-XX:+UseConcMarkSweepGC:设置年老代并发收集器
-XX:+UseG1GC:设置 G1 收集器,JDK1.9默认垃圾收集器