jvm(Java Virtual Machine): java虚拟机,我们编写的java文件通过javac编译成.class文件后,通过classloader加载到JVM中执行,所以说我们得代码都是在JVM中执行的;java的一处编写,到处运行(write once run everywhere) 就是通过JVM来实现的,如果我们的代码要在linux上执行,jvm就会将我们的class翻译成linux指令,在windows上执行,就会翻译成windows指令;在安装JVM时,我们要根据平台来安装对应的版本,windows需要安装windows版本JVM,linux需要安装linux版本。
jvm的主要功能:
1.将class文件转换为执行的指令
2.存储java程序运行时的数据。(既运行时数据区)
运行时数据区
线程独享区:
程序计数器:存储当前线程正在执行的字节码指令的地址或行号;由于java是多线程的,每个线程需要抢占CPU的时间片来执行,当线程从暂停到恢复执行时,通过此计数器来恢复上次执行的状态。
虚拟机栈:线程在每次执行方法的时候,都会创建一个栈帧,栈帧是出栈和入栈的最小单元;栈帧存储本地变量表、操作栈、动态链接、方法的出入口等。每一个方法调用的过程就是一个栈帧入栈到出栈的过程。
本地方法栈:与虚拟机栈类似,只不过本地方法栈存储的时native方法的变量、数据、操作数等信息。
线程共享区:
堆:存储java中的对象,这个区域是JVM中占用内存较大的一个区域,垃圾回收就是发生才此区域。
方法区:存储class的结构信息,方法的描述,常量池。在1.7之前存在方法区,1.8后改造为元空间,存在在直接内存中,而不是JVM的数据区;
堆分代:
按照对象的存活时间堆可以分为新生代和老年代,根据经验java中的大部分对象都是朝生夕死的,将堆分为新生代和老年代可以提高gc的效率。新生代又分为Eden区和survivor;
指针碰撞:
由于java是多线程执行的,如果多个线程同时去堆中创建对象,就有可能出现创建对象的并发问题,这个问题被称为指针碰撞。
虚拟机的解决方法是,为每个线程分配一个单独的区域用来创建对象,当这个区域不足时,会进行扩容。
JVM存活对象判定算法
1.引用计数法,记录每个对象被其他对象引用的次数,如果为0,表示是可以被回收的对象。缺点:不能解决对象循环引用的问题。
2.可达性分析算法:以一系列被称为GCroot的对象为起始点,从这个起始点开始搜索,搜索走过的路径称为引用链,不在搜索路径上的对象是可以被回收的对象;
可以称为GCroot的对象有:虚拟机栈中引用的对象;方法区中静态变量引用的对象;方法区中常量引用的对象;本地方法栈中JNI引用的对象。
在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为 强引用 (Strong Reference)、软引用 (Soft Reference)、弱引用 (Weak Reference)、虚引用 (Phantom Reference) 4 种,这 4 种引用强度依次逐渐减弱。
引用类型 | 说明 | 实现 |
强引用 | 只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。 | Object obj = new Object(); |
软引用 | 被软引用关联的对象,在系统即将发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。 | SoftReference<Object> sfRefer = new SoftReference<>(new Object()); sfRefer.get(); // 可以获得引用对象值 |
弱引用 | 它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次 GC 发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象 | WeakReference<Object> weakRefer = new WeakReference<>(new Object()); weakRefer.get(); // 可以获得引用对象值 |
虚引用 | 最弱的一种引用关系,被虚引用关联的对象,完全不会对其生存时间构成影响,也无法通过虚引用获取引用对象值。为应用设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 | PhantomReference<Object> phantom = new PhantomReference<>(new Object(), new ReferenceQueue<>()); |
垃圾回收算法:
标记清除算法:1.先标记堆中的可回收对象。2.清除可回收对象。优点是效率快,缺点是会产生内存碎片
复制回收算法;此算法会将内存分成两块,分配的时候只使用其中的一块,在回收的时把有效的对象复制到没有使用的内存区域,之后清空之前使用的区域。优点:解决了内存碎片的问题;缺点:内存的利用率低;
标记整理算法:1.先标记内存中的对象。2.清除垃圾对象3.将没有被回收的对象整理到一个连续的存储区域;解决了内存碎片的问题,但效率较低
新生代的垃圾收集器
Serial垃圾收集器
- 单线程:只开启一条GC回收线程,并且在回收过程中暂停一切用户线程,从而用户请求和图形界面会出现卡顿。
- 只适用于客户端应用:一般客户端应用所需的内存较小,不会创建太多对象,而且堆内存不大,因此垃圾回收时间比较短,即使在这段时间内停止一切用户线程,用户也不会感觉到卡顿,因此Serial垃圾收集器适合在客户端使用。
- 简单高效:由于Serial垃圾收集器只有一条GC回收线程,因此避免了回收线程切换的开销。
- 采用复制算法。

ParNew垃圾收集器
ParNew是Serial 的多线程版本
- 多线程并行执行:ParNew由多条GC线程并行的进行垃圾清理。但是清理过程仍然停止一切用户线程。但由于多条GC同时执行,清理速度比Serial收集器有所提高。
- 适合多CPU的服务器环境。
- 与Serial性能对比:ParNew唯一的区别就是使用了多线程回垃圾,在多CPU环境比Serial收集器的效率要高,但是线程切换会需要额外的开销,故在单CPU环境中,Serial的表现比PraNew好。
- 采用复制算法。
- 追求 -降低停止等待时间:和Serial相比,ParNew使用多线程的目的就是缩短垃圾收集时间,从而减少用户等待时间。
-
parNew
Parallel Scavenge垃圾收集器
Parallel Scavenge和ParNew都是 使用多线程、新生代垃圾收集器、都使用的复制算法进行垃圾回收。但是他们有个不同:ParNew追求较少用户等待时间,Parallel Scavenger追求CPU最大吞吐量,能在较短时间完成任务,因此适合没有交互的后台计算。
- 吞吐量:指用户线程运行的时间占CPU总时间的比例,CPU总时间包括:用户线程运行时间和GC线程运行时间,故吞吐量高表示用户线程运行时间越长,从而使用户请求被快速处理完。
- 吞吐量=用户线程运行时间/(用户线程时间+gc执行时间)
- 降低停顿时间的两种方式:①在多CPU环境中使用多条GC线程,从而垃圾收集时间减少,用户停顿也减少。②实现GC线程与用户线程的并发执行。
- Parallel Scavenge参数:-XX:GCTimeRadio设置垃圾回收时间占总CPU时间的百分比;-XX:MaxGCPauseMillis设置垃圾处理过程最久停顿时间。

老年代收集器
Serial old垃圾收集器
Serial Old是Serial的老年代版本,都是使用单线程,都适用于客户端,唯一的区别使用的是 标记-整理算法。

Parallel Old垃圾收集器
Parallel Old收集器是Parallel Scavenge的老年代版本,一般它们搭配使用,追求CPU吞吐量。
它们在垃圾收集时都是由多条GC线程并行执行,并停止一切用户线程。因此,由于在垃圾清理过程中没有使垃圾收集和用户线程并行执行,因此它们是追求吞吐量的垃圾收集器。

CMS垃圾收集器
CMS收集器是在老年代中追求停顿时间的收集器,他在垃圾收集时与用户线程并发的执行,因此垃圾回收过程中用户不会觉得明显的卡顿。但是用户线程和GC线程的切换有开销,垃圾回收时间会被延长。
- 吞吐量低
- 无法处理浮动垃圾
- 使用标记清理算法产生内存碎片。
- 垃圾回收过程:①初始标记②并发标记③重新标记④并发清除

收集器搭配方式
通用垃圾收集器G1垃圾收集器
目前最常用
G1特点
- 追求停顿时间
- 多线程GC
- 面向服务端应用
- 标记整理算法和复制算法合并,不会产生内存碎片
- 对整个堆进行垃圾回收
- 可预测停顿时间。
G1内存模型
G1中没有新生代和老年代的区别,将堆划分成一块块独立的Region。当进行垃圾收集时,估算Region的数量,每次从最大回收价值的Region开始回收,获得最大的回收率。
Remembered Set
每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,从而在进行可达性分析时,只要在GC ROOTs中再加上Remembered Set即可防止对所有堆内存的遍历。
垃圾收集的过程
- 初始标记
- 并发标记
- 最终标记
- 筛选回收