Java JVM
2024-03-18 11:54:10 1 举报
AI智能生成
Java虚拟机(JVM)是Java程序的运行时环境。
作者其他创作
大纲/内容
Java内存区域
运行时数据区域
概要
JDK 1.7
JDK 1.8
线程私有的
程序计数器、虚拟机栈、本地方法栈
线程共享的
堆、方法区、直接内存 (非运行时数据区的一部分)
程序计数器
一块较小的内存空间,作为当前线程所执行的字节码的行号指示器。
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,以便切换回来后继续执行。
唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期与线程保持一致。
Java 虚拟机栈
JVM 运行时数据区域的一个核心,生命周期与线程保持一致。
栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址。
拥有先进后出的数据结构,只支持出栈和入栈两种操作。
局部变量表
主要存放编译期可知的各种数据类型、对象引用。
操作数栈
主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。
计算过程中产生的临时变量也会放在操作数栈中。
动态链接
主要服务一个方法需要调用其他方法的场景。
作用
为了将符号引用转换为调用方法的直接引用,这个过程也被称为 动态连接 。
StackOverFlowError
栈的内存大小不允许动态扩展,栈深度超过最大深度
OutOfMemoryError
虚拟机在动态扩展栈时无法申请到足够的内存空间
本地方法栈
只为虚拟机使用到的 Native 方法服务的Java虚拟机栈。
堆
最大一块内存区域,在虚拟机启动时创建,唯一目的就是存放对象实例。
注意:随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,并不是所有对象都在堆中分配,JDK 1.7 默认开启逃逸分析。
垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。
结构
新生代和老年代
Eden、Survivor、Old
JDK 1.7
新生代(Young Generation)
Eden、S0、S1
老年代(Old Generation)
Tenured
永久代(Permanent Generation)
PermGen
JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存。
永久代替换为元空间原因
永久代受 JVM 固定大小限制,元空间使用的本地内存,溢出几率小。
存放类的元数据,加载类数量不由 MaxPermSize 控制,而由系统空间控制,可加载更多类。
JDK8 合并 HotSpot 和 JRockit 的代码,JRockit 没有永久代。
元空间不指定大小时,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
对象在 Eden 区域分配,首次GC后若存活则进入 S0 或 S1,并且对象的年龄还会加 1,年龄超过设置(默认15)则晋升到老年代中。
OutOfMemoryError
GC Overhead Limit Exceeded:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
Java heap space:假如在创建新的对象时,堆内存中的空间不足以存放新创建的对象,就会引发此错误。
MetaSpace:当元空间溢出时,就会引发此错误。
方法区
JVM 运行时数据区域的一块逻辑区域。
类加载时,存储类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
方法区和永久代以及元空间关系
类似 Java 中接口和类的关系
方法区看作接口,永久代以及元空间为不同实现。
运行时常量池
常量池表(Constant Pool Table)
用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference)
字面量包括整数、浮点数和字符串字面量。
符号引用包括类符号引用、字段符号引用、方法符号引用、接口方法符号。
常量池表会在类加载后存放到方法区的运行时常量池中。
运行时常量池是方法区的一部分,受到方法区内存的限制,也会抛出 OutOfMemoryError 错误。
字符串常量池
JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
原理
固定大小的HashTable ,容量为 StringTableSize(可以通过 -XX:StringTableSize 参数来设置)。
保存的是字符串(key)和 字符串对象的引用(value)的映射关系,字符串对象的引用指向堆中的字符串对象。
JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中。
JDK 1.7 将字符串常量池移动到堆中原因
永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。
Java 程序中通常会有大量的被创建的字符串等待回收,放在堆中,能更高效回收。
直接内存
一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
不属于虚拟机。
堆外内存
把内存对象分配在堆外的内存,这些内存直接受操作系统管理(而不是虚拟机),一定程度上减少垃圾回收对应用程序造成的影响。
HotSpot 虚拟机
对象的创建过程
类加载检查
检查类的符号引用是否能在常量池中定位,再检查类是否已被加载过、解析和初始化过,如没有就需要执行类加载过程。
分配内存
虚拟机将为新生对象分配内存。
对象所需的内存大小在类加载完成后便可确定。
方式
指针碰撞
适用场合:堆内存规整(即没有内存碎片)的情况下。
原理:内存用过和没用过的分两边,中间一个分界指针,分配时,向没用过区域移动指针即可。
使用该分配方式的 GC 收集器:Serial, ParNew
空闲列表
适用场合:堆内存不规整的情况下。
原理:虚拟机维护了会记录哪些内存块是可用的列表,分配时,更新列表即可。
使用该分配方式的 GC 收集器:CMS
Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩")。
内存分配并发问题
虚拟机采用两种方式来保证线程安全。
CAS+失败重试: CAS 是乐观锁的一种实现方式。
TLAB:首先在 TLAB 分配,当 TLAB 的内存不够时,再采用上述的 CAS 进行内存分配。
初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)
目的:保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置。
例如:这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。
执行 init 方法
初始化后,一个真正可用的对象才算完全产生出来。
对象的内存布局
对象头
用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等)。
类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据
对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充
不是必然存在的,也没有什么特别的含义,仅仅起占位作用。
原因:虚拟机要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。
对象的访问定位
Java 程序通过栈上的 reference 数据来操作堆上的具体对象,对象的访问方式由虚拟机实现而定。
句柄
Java 堆分配句柄池,reference 存储对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。
优势:reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。
直接指针
使用直接指针访问,reference 中存储的直接就是对象的地址。
优势:速度快,它节省了一次指针定位的时间开销。
JVM垃圾回收
内存分配和回收原则
对象优先在 Eden 区分配
新生代无足够空间时,虚拟机通过 分配担保机制 把新生代的对象提前转移到老年代中。
大对象直接进入老年代
优化策略:减少新生代的垃圾回收频率和成本。
长期存活的对象将进入老年代
对象年龄(Age)计数器,每次GC,年龄就增加1,年龄超过设置(默认15)则晋升到老年代中。
主要进行 GC 的区域
部分收集 (Partial GC)
新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集
老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。
混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
整堆收集 (Full GC):收集整个 Java 堆和方法区。
空间分配担保
为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。
死亡对象判断方法
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
引用计数法
给对象中添加一个引用计数器,引用+1,引用失效-1,0代表无引用。
简单高效,但使用少,原因:很难解决对象之间循环引用的问题。
可达性分析算法
以 “GC Roots” 的对象为起点向下搜索,节点所走过的路径称为引用链,当对象到 GC Roots 没有任何引用链相连则证明此对象不可达。
可以作为 GC Roots 的对象
虚拟机栈(栈帧中的局部变量表)中引用的对象
本地方法栈(Native 方法)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁持有的对象
JNI(Java Native Interface)引用的对象
不可达对象在死亡前,仍需要经历两次标记才能真正死亡被回收:finalize 方法和队列标记。
引用类型总结
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
强引用(StrongReference)
我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象。
大部分引用实际上都是强引用,这是使用最普遍的引用。
软引用(SoftReference)
使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收。
可用来实现内存敏感的高速缓存。
可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
弱引用(WeakReference)
使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收。
弱引用与软引用的区别:只具有弱引用的对象拥有更短暂的生命周期。
虚引用(PhantomReference)
虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知。
主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别:虚引用必须和引用队列(ReferenceQueue)联合使用。
除了强引用外,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多。
废弃常量
没有任何对象引用的常量就是废弃常量,如果这时发生内存回收的话而且有必要的话,废弃常量就会被系统清理。
无用的类
条件
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足条件的无用的类进行回收,而不是必然被回收。
垃圾收集算法
标记-清除(Mark-and-Sweep)算法
首先标记(Mark)出所有不需要回收的对象,在标记完成后统一清除(Sweep)掉所有没有被标记的对象。
最基础的收集算法,后续的算法都是对其不足进行改进得到。
问题
效率问题:标记和清除两个过程效率都不高。
空间问题:标记清除后会产生大量不连续的内存碎片。
复制(Copying)算法
将内存分为大小相同的两块A和B,只使用A,当A使用完后,就将还存活的对象复制到B,然后再把A一次清理掉。
问题
可用内存变小:可用内存缩小为原来的一半。
不适合老年代:如果存活对象数量比较大,复制性能会变得很差。
标记-整理(Mark-and-Compact)算法
标记过程与“标记-清除”算法一样,但后续是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。
分代收集算法
根据对象存活周期的不同将内存分为几块(新生代和老年代),以便根据各个年代的特点选择合适的垃圾收集算法。
当前虚拟机的垃圾收集都采用分代收集算法。
垃圾收集器
没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。
JDK 默认垃圾收集器
JDK 8:Parallel Scavenge(新生代)+ Parallel Old(老年代)
JDK 9 ~ JDK20: G1
Serial 收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器。
单线程收集器,只会使用一条线程去完成工作同时,必须暂停其他所有的工作线程( "Stop The World" ),直到它工作结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
优点:简单而高效(与其他收集器的单线程相比),运行在 Client 模式下的虚拟机的不错选择。
ParNew 收集器
Serial 收集器的多线程版本,运行在 Server 模式下的虚拟机的首选。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Parallel Scavenge 收集器
几乎和 ParNew 收集器一样,但其关注点是吞吐量(高效率的利用 CPU),其它关注更多的是用户线程的停顿时间(提高用户体验)。
吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。
新生代采用标记-复制算法,老年代采用标记-整理算法。
JDK1.8 默认使用的垃圾收集器是 Parallel Scavenge + Parallel Old。
Serial Old 收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。
用途
在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用。
作为 CMS 收集器的后备方案。
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。
在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS(Concurrent Mark Sweep)收集器
以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
步骤
初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快。
并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。
重新标记: 为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
优点
并发收集、低停顿。
缺点
对 CPU 资源敏感。
无法处理浮动垃圾。
使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
从 JDK9 开始,CMS 收集器已被弃用。
G1(Garbage-First) 收集器
面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。
特点
并行与并发、分代收集、空间整合、可预测的停顿
步骤
初始标记、并发标记、最终标记、筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region。
从 JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器。
ZGC 收集器
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
ZGC 可以将暂停时间控制在几毫秒以内,且暂停时间不受堆内存大小的影响,出现 Stop The World 的情况会更少,但代价是牺牲了一些吞吐量。
ZGC 在 Java11 中引入,处于试验阶段,在 Java15 已经可以正式使用了,最大支持 16TB 的堆内存。但默认的垃圾回收器依然是 G1。
类文件结构
魔数(Magic Number)
每个 Class 文件的头 4 个字节称为魔数(Magic Number)。
唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件。
Java 规范规定魔数为固定值:0xCAFEBABE。非此值,Java 虚拟机将拒绝加载它。
Class 文件版本号(Minor&Major Version)
魔数后的四个字节存储的是 Class 文件的版本号:第 5 ~ 6 个字节是次版本号,第 7 ~ 8 个字节是主版本号。
每当 Java 发布大版本(比如 Java 8,Java9)的时候,主版本号都会加 1。可以使用 javap -v 命令来快速查看 Class 文件的版本号信息。
常量池(Constant Pool)
主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1。
主要存放两大常量:字面量和符号引用。
字面量
接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。
符号引用
属于编译原理方面的概念。包括 类和接口的全限定名、字段的名称和描述符、方法的名称和描述符 这三类常量。
访问标志(Access Flags)
在常量池之后,紧接着的两个字节代表访问标志。
用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。
当前类(This Class)、父类(Super Class)、接口(Interfaces)索引集合
Java 类的继承关系由类索引、父类索引和接口索引集合三项确定。
字段表集合(Fields)
字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
方法表集合(Methods)
方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
属性表集合(Attributes)
在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
类加载过程
类的生命周期
加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。
验证、准备和解析这三个阶段可以统称为连接(Linking)。
类加载过程
系统加载 Class 类型的文件主要三步:加载->连接->初始化。
连接过程又可分为三步:验证->准备->解析。
加载
通过全类名获取定义此类的二进制字节流。
将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。
验证
确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求。
组成
文件格式验证(Class 文件格式检查)
元数据验证(字节码语义检查)
字节码验证(程序语义检查)
符号引用验证(类的正确性检查)
准备
正式为类变量分配内存并设置类变量初始值的阶段,在方法区中分配。
解析
虚拟机将常量池内的符号引用替换为直接引用的过程。
主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符 7 类符号引用进行。
初始化
执行初始化方法 <clinit> ()方法的过程,是类加载的最后一步。
必须对类进行初始化的6种情况
当遇到 new、 getstatic、putstatic 或 invokestatic 这 4 条字节码指令时。
使用 java.lang.reflect 包的方法对类进行反射调用时。
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类)。
MethodHandle 和 VarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用,
就必须先使用 findStaticVarHandle 来初始化要调用的类。
就必须先使用 findStaticVarHandle 来初始化要调用的类。
当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时。
注意
<clinit> ()方法是编译之后自动生成的。
<clinit> () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个线程阻塞,并且这种阻塞很难被发现。
类卸载
卸载类即该类的 Class 对象被 GC。
要求
该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
该类没有在其他任何地方被引用。
该类的类加载器的实例已被 GC。
类加载器
定义
类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
每个 Java 类都有一个引用指向加载它的 ClassLoader。
数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。
作用
加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。
除了加载类,还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源。
加载规则
JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。
类加载器
BootstrapClassLoader(启动类加载器)
最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级。
作用
加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 jar 包和类)及被 -Xbootclasspath 参数指定的路径下的所有类。
拓展
rt.jar:rt 代表“RunTime”,rt.jar是 Java 基础类库,常用内置库 java.xxx.*都在里面。
ExtensionClassLoader(扩展类加载器)
加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
Java 9 引入了模块系统,并且略微更改了上述的类加载器。扩展类加载器被改名为平台类加载器(platform class loader)。
AppClassLoader(应用程序类加载器)
面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
自定义类加载器
需要继承 ClassLoader抽象类。
重写
loadClass(String name, boolean resolve) 方法
打破双亲委派模型
findClass(String name)方法
建议,不想打破双亲委派模型
线程上下文类加载器(ThreadContextClassLoader)
将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。
双亲委派模型
定义
ClassLoader 类使用委托模型来搜索类和资源。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
双亲委派模型并不是一种强制性的约束,只是 JDK 官方推荐的一种方式。
面向对象编程设计原则:组合优于继承,多用组合少用继承。(非常经典)
执行流程
在类加载的时候,系统会首先判断当前类是否被加载过。
类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
如果子类加载器也无法加载这个类,那么它会抛出一个 ClassNotFoundException 异常。
拓展
JVM 判定两个 Java 类是否相同的具体规则
类的全名和加载此类的类加载器都一致。
优点
保证了 Java 程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
打破模型
自定义类加载器并重写 loadClass(String name, boolean resolve) 方法。
单纯依靠自定义类加载器没办法满足某些场景的要求,例如,有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。
应用
Tomcat 服务器为了能够优先加载 Web 应用目录下的类。
SPI 接口的实现(如com.mysql.cj.jdbc.Driver)是由第三方供应商提供的,需要先加载。
JVM参数
堆内存相关
显式指定堆内存–Xms和-Xmx
-Xms<heap size>[unit]
-Xmx<heap size>[unit]
-Xmx<heap size>[unit]
heap size 表示要初始化内存的具体大小。
unit 表示要初始化内存的单位。单位为 “ g” (GB)、“ m”(MB)、“ k”(KB)。
显式新生代内存(Young Generation)
默认情况下,YG 的最小大小为 1310 MB,最大大小为 无限制。
方式
通过-XX:NewSize和-XX:MaxNewSize指定。
通过-Xmn<young size>[unit]指定。
通过 -XX:NewRatio=<int> 来设置老年代与新生代内存的比值。
GC调优策略
最大限度将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC。
显式指定永久代/元空间的大小
永久代
通过-XX:PermSize=N和-XX:MaxPermSize=N指定。
元空间
通过-XX:MetaspaceSize=N和-XX:MaxMetaspaceSize=N指定。
注意
Metaspace 的初始容量并不是 -XX:MetaspaceSize 设置,对于64位 JVM 来说,初始容量都是 21807104(约 20.8m)。
MetaspaceSize 表示 Metaspace 使用过程中触发 Full GC 的阈值,只对触发起作用。
垃圾收集相关
垃圾回收器
通过-XX:+Use<垃圾回收器标识>指定。
串行垃圾收集器(SerialGC)、并行垃圾收集器(ParallelGC)、CMS 垃圾收集器(ParNewGC)、G1 垃圾收集器(G1GC)
GC 日志记录
通过-XX:+Print<打印内容标识>指定。
# 必选
# 打印基本 GC 信息
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
# 打印对象分布
-XX:+PrintTenuringDistribution
# 打印堆数据
-XX:+PrintHeapAtGC
# 打印Reference处理信息
# 强引用/弱引用/软引用/虚引用/finalize 相关的方法
-XX:+PrintReferenceGC
# 打印STW时间
-XX:+PrintGCApplicationStoppedTime
# 可选
# 打印safepoint信息,进入 STW 阶段之前,需要要找到一个合适的 safepoint
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation
# 最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=50M
# 打印基本 GC 信息
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
# 打印对象分布
-XX:+PrintTenuringDistribution
# 打印堆数据
-XX:+PrintHeapAtGC
# 打印Reference处理信息
# 强引用/弱引用/软引用/虚引用/finalize 相关的方法
-XX:+PrintReferenceGC
# 打印STW时间
-XX:+PrintGCApplicationStoppedTime
# 可选
# 打印safepoint信息,进入 STW 阶段之前,需要要找到一个合适的 safepoint
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
# GC日志输出的文件路径
-Xloggc:/path/to/gc-%t.log
# 开启日志文件分割
-XX:+UseGCLogFileRotation
# 最多分割几个文件,超过之后从头文件开始写
-XX:NumberOfGCLogFiles=14
# 每个文件上限大小,超过就触发分割
-XX:GCLogFileSize=50M
处理 OOM
JVM 提供了一些参数,将堆内存转储到一个物理文件中,以后可以用来查找泄漏。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./java_pid<pid>.hprof
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"
-XX:+UseGCOverheadLimit
-XX:HeapDumpPath=./java_pid<pid>.hprof
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >"
-XX:+UseGCOverheadLimit
其他
- -server : 启用“ Server Hotspot VM”; 此参数默认用于 64 位 JVM
- -XX:+UseStringDeduplication : Java 8u20 引入了这个 JVM 参数,通过创建太多相同 String 的实例来减少不必要的内存使用; 这通过将重复 String 值减少为单个全局 char [] 数组来优化堆内存。
- -XX:+UseLWPSynchronization: 设置基于 LWP (轻量级进程)的同步策略,而不是基于线程的同步。
- -XX:LargePageSizeInBytes: 设置用于 Java 堆的较大页面大小; 它采用 GB/MB/KB 的参数; 页面大小越大,我们可以更好地利用虚拟内存硬件资源; 然而,这可能会导致 PermGen 的空间大小更大,这反过来又会迫使 Java 堆空间的大小减小。
- -XX:MaxHeapFreeRatio : 设置 GC 后, 堆空闲的最大百分比,以避免收缩。
- -XX:SurvivorRatio : eden/survivor 空间的比例, 例如-XX:SurvivorRatio=6 设置每个 survivor 和 eden 之间的比例为 1:6。
- -XX:+UseLargePages : 如果系统支持,则使用大页面内存; 请注意,如果使用这个 JVM 参数,OpenJDK 7 可能会崩溃。
- -XX:+UseStringCache : 启用 String 池中可用的常用分配字符串的缓存。
- -XX:+UseCompressedStrings : 对 String 对象使用 byte [] 类型,该类型可以用纯 ASCII 格式表示。
- -XX:+OptimizeStringConcat : 它尽可能优化字符串串联操作。
JDK工具
命令行工具
jps (JVM Process Status): 类似 UNIX 的 ps 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息。
jstat(JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据。
jinfo (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息。
jmap (Memory Map for Java) : 生成堆转储快照。
jhat (JVM Heap Dump Browser) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。
jstack (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
可视化分析工具
JConsole:Java 监视与管理控制台
Visual VM:多合一故障处理工具
JVM问题及调优案例
0 条评论
下一页