1 JVM定义
JVM(Java Virtual Machine)是一个虚拟出来的机器,是运行所有Java程序的抽象计算机,
2 JDK、JRE与JVM
- JRE(Java Runtime Environment),Java 运行时环境,内部包含了Java虚拟机(JVM)以及Java核心类库等运行Java程序的必要组件,计算机中只要安装了JRE就可以运行编译好的java程序。
- JDK(Java Development Kit),Java 开发工具包,内部包含了JRE,以及编译工具javac,打包工具jar,Java基础类库(Java API)等。
3、JVM架构
JVM 中主要有三个子系统
- 类加载器子系统(Class Loader Sub System)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
3.1 类加载器子系统
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
3.1.1 类加载流程
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载-》验证=》准备=》解析=》初始化=》使用=》卸载这7个阶段,其中其中验证、准备、解析3个部分统称为连接。
类加载阶段 | 作用 |
---|---|
加载 | 通过一个类的全限定名加载该类对应的二进制字节流。将字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法区各个类访问该类的入口。 |
验证 | 文件格式验证;元数据的验证;字节码校验;符号引用验证 |
准备 | 1.为类变量(static修饰的变量)分配内存并且设置该类变量的默认初始值,即零值;2.这里不会为实例变量分配初始化,类变量会分配在方法区,而实例变量是会随着对象一起分配给Java堆中 |
解析 | 解析阶段是虚拟机将常量池内的符号引用(类、变量、方法等的描述符 [名称])替换为直接引用 |
初始化 | 初始化阶段编译器会将类文件声明的静态赋值变量和静态代码块合并生成方法并进行调用。 |
3.1.2类加载器分类以及作用
类加载器类别 | 作用 |
---|---|
启动类加载器(Bootstrap ClassLoader) | 用来加载 Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚 拟机识别的类库; |
扩展类加载器(Extension ClassLoader) | 负责加载\lib\ext目录或Java. ext. dirs( -Djava.ext.dirs)系统变量指定的路径中的所有类库 |
应用程序类加载器(Application ClassLoader) | 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我 们没有自定义类加载器默认就是用这个加载器 |
3.1.3 双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。如下图所示:
3.2 运行时数据区
Java虚拟机在执行Java程序时,会把它管理的内存划分为若干不同的数据区域。这区域各有各的用途以及生命周期。
以线程公有区域和线程私有区域可以划分为两个部分
数据区域 | 作用 |
---|---|
方法区 | 其中主要存储类的类型信息,方法信息,域信息,JIT代码缓存,运行时常量池等。元空间与永久代的最大区别就是:元空间不在虚拟机设置的内存中,而是直接使用的本地内存 |
堆内存(Java堆) | 几乎所有的对象实例都在这里分配内存。字符串常量池(String Table),静态变量也在这里分配内存;java堆是垃圾收集器管理的内存区域; Java堆在逻辑上应该认为是连续的,但是在具体的物理实现上,可以是不连续的;Java堆可以是固定大小的,也可以是可扩展的。现在主流Java虚拟机都是可扩展的;-Xmx 最大堆内存-Xms 最小堆内存;Eden 区占大容量,Survivor 两个区占小容量,默认比例是 8:1:1; |
Java虚拟机栈 | java虚拟机栈是线程私有的,其生命周期和线程相同;虚拟机栈描述的是Java方法执行的线程内存模型,每个方法被执行,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、参与方法的调用与返回等。每一个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中出入栈到出栈的过程;JVM 允许指定 Java 栈的初始大小以及最大、最小容量。 |
程序计数器 | 1.程序计数器其实就是一个指针,它指向了我们程序中下一句需要执行的指令;2.不管是分支、循环、跳转等代码逻辑,字节码解释器在工作时就是改变程序计数器的值来决定下一条要执行的字节码;3.每个线程都有一个独立的程序计数器,在任意一个确定的时刻,一个CPU内核都只会执行一条线程中的指令;4.程序计数器占用内存空间小到基本可以忽略不计;5.如果正在执行的是Native方法,则这个计数器为空 |
本地方法栈 | 只不过虚拟机栈为虚拟机执行的Java方法(即字节码)服务,本地方法栈为虚拟机执行的本地方法(Native方法、C/C++ 实现)服务。 |
3.3 执行引擎
模块 | 作用 |
---|---|
解释器 | 当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令然后执行。 |
即时(JIT)编译器 | 就是虚拟机将Java字节码一次性整体编译成和本地机器平台相关的机器语言,但并不是马上执行。JIT 编译器将字节码翻译成本地机器指令后,就可以做一个缓存操作,存储在方法区 的 JIT 代码缓存中 |
垃圾收集器 | 用来进行垃圾回收 |
3.3.1 垃圾回收器
1、JVM中的垃圾
简单的说垃圾就是内存中不再使用的对象,所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而不再使用的对象(未引用对象),则没有被任何指针指向。如果这些不再使用的对象不被清除掉,我们内存里面的对象会越来越多,而可使用的内存空间会越来越少,最后导致无空间可用。
GC分类
GC类别 | 特点 |
---|---|
Minor GC | 指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。 |
Full GC | 指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。 |
垃圾回收的基本步骤分两步:
(1)查找内存中不再使用的对象(GC判断策略)
(2)释放这些对象占用的内存(GC收集算法)
2、GC判断策略
算法 | 定义 | 优点 | 缺点 |
---|---|---|---|
引用计数算法 | 给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,任何时候当计数器为0的时候,该对象不再被引用。 | 引用计数器这个方法实现简单,判定效率也高,回收没有延迟性 | 无法检测出循环引用 |
可达性分析算法 | 从所有的GC Roots节点开始,寻找该节点所引用的节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。 | 实现简单和执行高效,有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生 |
GC Roots主要包括
- 虚拟机栈中引用的对象(各个线程被调用的方法中使用到的参数、局部变量等)
- 本地方法栈内 JNI(通常说的本地方法)引用的对象
- 方法区中类静态属性引用的对象(Java 类的引用类型静态变量)
- 方法区中常量引用的对象(字符串常量池里的引用)
- 所有被同步锁 synchronized 持有的对象
- 虚拟机的内部引用如基本数据类型对应的 Class 对象,类加载器、异常管理对象;
- 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
3 GC收集算法
算法 | 定义 | 优点 | 缺点 |
---|---|---|---|
标记-清除算法 | 标记-清除算法的基本思想就跟它的名字一样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象 | 实现简单 | 效率低,内存碎片化严重,后续可能发生比较大的对象没法找到可用的内存区域 |
复制算法 | 复制算法是将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉。 | 1简单高效;2优化了标记清除算法的效率低、内存碎片多问题 | 1.将内存缩小为原来的一半,浪费了一半的内存空间;2.如果对象的存活率很高,极端一点的情况假设对象存活率为 100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的 |
标记-整理算法 | 此清理算法综合了前面两种,在标记阶段和 标记-清除算法一样,标记后不是清理对象,而是将存活的对象移动到内存的一端,然后清除边际外的内存空间。 | 充分利用内容空间,解决内存碎片化问题 | 在效率上不如复制算法 |
分代收集算法 | 其中新生代的回收算法以复制算法为主,老年代的回收算法以标记-清除以及标记-整理为主。 | 整合前面几种算法的优点,合理利用资源 |
4垃圾收集器
垃圾收集器是GC收集算法的具体实现。根据具体的场景选择适合的垃圾收集器。
垃圾收集器类型 | 参数 | 算法 | 特点 | 适用场景 |
---|---|---|---|---|
Serial | -XX:+UseSerialGC | 复制算法 | Serial是一个新生代收集器,使用的是标记复制算法,Serial收集器是最基础也是历史最悠久的收集器,Serial是属于单线程垃圾回收器,在进行垃圾回收时会暂停所有的用户线程,直到垃圾回收结束(Stop The World)。 | 在单个CPU环境下,Serial收集器不需要进行额外的线程切换,有更高的效率。 |
ParNew | -XX:+UseParNewGC | 复制算法 | ParNew收集器就是Serial收集器的多线程版本, 两者的区别就是Serial使用单线程进行垃圾回收,ParNew使用多条线程进行垃圾回收,两者使用的垃圾回收算法、Stop The World和回收策略等其他方面都完全一样。 | 多cpu环境下,ParNew收集器对资源的有效利用越来越好。 |
Parallel Scavenge | -XX:+UseParallelGC | 复制算法 | arallel收集器其实就是Serial收集器的多线程版;parallel收集器关注的是吞吐量(高效利用cpu) | 吞吐量高的要求的系统 |
CMS | -XX:+UseConcMarkSweepGC | 标记-清除算法 | 是一种以获得最短回收停顿时间为目标的收集器;Serial收集器的多线程版,关注停顿时间 | 低延时的系统 |
Serial Old | -XX:+UseSerialOldGC | 标记整理算法 | Serial Old收集器就是Serial收集器的老年代版本,同样是一个单线程的收集器 | 单cpu系统 |
Parallel Old | -XX:+UseParallelOldGc | 标记整理算法 | 该收集器就是Parallel Scavenge的老年代版本,功能上也是类似,只不过该收集器的收集区域是老年代而已 | 关注吞吐量的系统 |
G1 | -XX:+UseG1GC | 标记-整理算法 | G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆空间划分为多个大小相等的独立区域(Region),每个Region都可以成为 Eden空间、Survivor空间、老年代空间。是一种既适应与新生代,又适应与老年代的垃圾回收器 | 对JVM参数不熟悉,希望实现自动化jvm调优 |
4.JVM常见参数
名称 | 作用 |
---|---|
-Xmn | 新生代内存大小,包括E区和两个S区的总和 |
-Xms | 初始堆的大小,也是堆大小的最小值,默认值是总共的物理内存/64 |
-Xmx | 堆的最大值,默认值是总共的物理内存/64(且小于1G) |
-Xss | 每个线程的栈内存,默认1M) |
-Xprof | 跟踪正运行的程序,并将跟踪数据在标准输出;适合于开发环境调试 |
-Xnoclassgc | 关闭针对class的gc功能 |
-Xincgc | 开启增量gc(默认为关闭);这有助于减少长时间GC时应用程序出现的停顿 |
-Xloggc:file | 与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中如-Xloggc:/log/ajgl/gc-%t.log |
XX:NewSize=2.125m | 新生代对象生成时占用内存的默认值 |
-XX:MaxNewSize=size | 新生成对象能占用内存的最大值 |
-XX:MaxPermSize=64m | 方法区所能占用的最大内存(非堆内存) |
-XX:PermSize=64m | 方法区分配的初始内存 |
-XX:MaxTenuringThreshold=15 | 对象在新生代存活区切换的次数(坚持过MinorGC的次数,每坚持过一次,该值就增加1),大于该值会进入老年代(年龄阈值) |
-XX:MaxHeapFreeRatio=70 | GC后java堆中空闲量占的最大比例,大于该值,则堆内存会减少 |
-XX:MinHeapFreeRatio=40 | GC后java堆中空闲量占的最小比例,小于该值,则堆内存会增加 |
-XX:NewRatio=2 | 新生代内存容量与老生代内存容量的比例 |
-XX:ReservedCodeCacheSize= 32m | 保留代码占用的内存容量 |
-XX:ThreadStackSize=512 | 设置线程栈大小,若为0则使用系统默认值 |
-XX:LargePageSizeInBytes=4m | 设置用于Java堆的大页面尺寸 |
-XX:PretenureSizeThreshold= size | 大于该值的对象直接晋升入老年代(这种对象少用为好) |
-XX:SurvivorRatio=8 | Eden区域Survivor区的容量比值,如默认值为8,代表Eden:Survivor1:Survivor2=8:1:1 |
-XX:+UseSerialGC | 启用串行GC,即采用Serial+Serial Old模式 |
-XX:+UseParallelGC | 启用并行GC,即采用Parallel Scavenge+Serial Old收集器组合(-Server模式下的默认组合) |
XX:GCTimeRatio=99 | 设置用户执行时间占总时间的比例(默认值99,即1%的时间用于GC) |
-XX:MaxGCPauseMillis=time | 设置GC的最大停顿时间(这个参数只对Parallel Scavenge有效) |
-XX:+UseParNewGC | 使用ParNew+Serial Old收集器组合 |
-XX:ParallelGCThreads | 设置执行内存回收的线程数,在+UseParNewGC的情况下使用 |
-XX:+UseParallelOldGC | 使用Parallel Scavenge +Parallel Old组合收集器 |
-XX:+UseConcMarkSweepGC | 使用ParNew+CMS+Serial Old组合并发收集,优先使用ParNew+CMS,当用户线程内存不足时,采用备用方案Serial Old收集。 |
-XX:-DisableExplicitGC | 禁止调用System.gc();但jvm的gc仍然有效 |
-XX:+ScavengeBeforeFullGC | 新生代GC优先于Full GC执行 |
-XX:+UseG1GC | 使用G1垃圾回收器 |
-XX:-CITime | 打印消耗在JIT编译的时间 |
-XX:ErrorFile=./hs_err_pid.log | 保存错误日志或者数据到文件中 |
-XX:-ExtendedDTraceProbes | 开启solaris特有的dtrace探针 |
-XX:HeapDumpPath=./java_pid.hprof | 指定导出堆信息时的路径或文件名 |
-XX:-HeapDumpOnOutOfMemoryError | 当首次遭遇OOM时导出此时堆中相关信息 |
-XX:OnError=“;” | 出现致命ERROR之后运行自定义命令 |
-XX:OnOutOfMemoryError=“;” | 当首次遭遇OOM时执行自定义命令 |
-XX:-PrintClassHistogram | 遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同 |
-XX:-PrintConcurrentLocks | 遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同 |
-XX:-PrintCommandLineFlags | 打印在命令行中出现过的标记 |
-XX:-PrintCompilation | 当一个方法被编译时打印相关信息 |
-XX:-PrintGC | 每次GC时打印详细信息 |
-XX:+PrintGCDetails | 每次GC时打印详细信息 |
-XX:-PrintGCTimeStamps | 打印每次GC的时间戳 |
-XX:-TraceClassLoading | 跟踪类的加载信息 |
-XX:-TraceClassLoadingPreorder | 跟踪被引用到的所有类的加载信息 |
-XX:-TraceClassResolution | 跟踪常量池 |
-XX:-TraceClassUnloading | 跟踪类的卸载信息 |
-XX:-TraceLoaderConstraints | 跟踪类加载器约束的相关信息 |
5 jvm四种引用类型
引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
软引用 | 当内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | 正常垃圾回收时 | 对象缓存 | 垃圾回收后终止 |
虚引用 | 正常垃圾回收时 | 跟踪对象的垃圾回收 | 垃圾回收后终止 |
强引用的示例
public void fun1() {
Object object = new Object();
Object[] objArr = new Object[1000];
}
弱引用的示例
public static void main(String[] args) {
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<Object>(obj);
System.out.println(obj);
System.out.println(softRef.get());
// 对象要设置为null,否则不会被回收。原因:通过设置为null让对象失去引用,方便GC
// 备注:因为在这个main方法中(主线程),方法未结束之前,不设置为null,对象是不会失去引用的。
obj = null;
// 当内存不足时,会自动触发GC操作,这里就无需手动GC
try {
byte[] b = new byte[30 * 1024 * 1024];
} catch (Exception e) {
// TODO: handle exception
} finally {
System.out.println(obj);
System.out.println(softRef.get());
}
}
弱引用示例
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<Object>(obj);
System.out.println(obj); // java.lang.Object@7852e922
System.out.println(weakRef.get()); // java.lang.Object@7852e922
// 对象要设置为null,否则不会被回收。原因:通过设置为null让对象失去引用,方便GC
// 备注:因为在这个main方法中(主线程),方法未结束之前,不设置为null,对象是不会失去引用的。
obj = null;
// 这里通过手动触发GC操作。否则内存充足的情况下很难自动触发GC
System.gc();
System.out.println(obj); // null
System.out.println(weakRef.get()); // null
}
虚引用示例
public static void main(String[] args) {
Object obj = new Object();
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(obj, queue);
System.out.println(pr.get()); // null
}
引用队列使用
public static void main(String[] args) {
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
WeakReference<Object> weakRef = new WeakReference<Object>(obj, queue);
System.out.println(obj); // java.lang.Object@7852e922
System.out.println(weakRef.get()); // java.lang.Object@7852e922
System.out.println(queue.poll()); // null
obj = null;
System.gc();
System.out.println(obj); // null
System.out.println(weakRef.get()); // null
System.out.println(queue.poll()); // java.lang.ref.WeakReference@4e25154f
}