兰州网站备案,商标如何自己注册,网站做友链的好处,电影网站膜拜一、JVM 概述 JVM#xff08;Java Virtual Machine#xff09;是 Java 程序运行的核心组件#xff0c;它提供了一个独立于硬件和操作系统的执行环境#xff0c;使得 Java 程序能够在不同平台上具有跨平台的特性。
JVM 主要由以下几部分组成#xff1a;
类装载器#xf…一、JVM 概述 JVMJava Virtual Machine是 Java 程序运行的核心组件它提供了一个独立于硬件和操作系统的执行环境使得 Java 程序能够在不同平台上具有跨平台的特性。
JVM 主要由以下几部分组成
类装载器Class Loader负责从文件系统或者网络加载 Java 类转换成 Java 字节码然后加载到运行时数据区。运行时数据区这是 Java 虚拟机执行 Java 程序时使用的主要内存空间主要包括以下几部分 方法区Method Area存储已被加载的类信息常量静态变量即时编译器编译后的代码等数据。堆Heap这是 JVM 所管理的最大的一块内存空间几乎所有的对象实例都在这里分配内存。Java 栈Java Stacks每个线程都有一个私有的 Java 栈用于存储局部变量操作数栈动态链接和方法出口等信息。本地方法栈Native Method Stacks对于执行 Native 方法服务的栈每个线程都会有一个对应的本地方法栈。程序计数器Program Counter Register它是当前线程所执行的字节码的行号指示器。 执行引擎Execution Engine负责执行字节码主要包括解释器、即时编译器JIT以及垃圾回收器。本地接口库Native InterfaceJava Native Interface可以支持 Java 调用其他语言的程序。本地方法库Native Method Library存储了所有的本地方法由 JVM 调用。
Java 虚拟机屏蔽了与具体操作系统平台相关的信息使得 Java 程序只需生成在 Java 虚拟机上运行的目标代码字节码就可以在多种平台上不加修改地运行。Java 虚拟机有自己完善的硬体架构如处理器、堆栈、寄存器等还具有相应的指令系统。
Java 语言的跨平台特性是由 JVM 实现的我们编写的程序运行在 JVM 上JVM 运行在操作系统上。Java 编译将.java 文件转化成.class 文件.class 文件中并不是机器码而是字节码。这种字节码是不依赖于计算机硬件架构而存在的不管是什么操作系统只要有对应的 JVM字节码就可以在任何平台运行真正实现了一次编译到处运行。
JVM 的整体结构架构模型有基于栈式和基于寄存器两种。基于栈式的指令集架构设计和实现更简单适用于资源受限的系统避开了寄存器的分配难题不需要硬件支持可移植性更好更好实现跨平台基于寄存器的指令集架构则完全依赖硬件可移植性差但性能优秀和执行更高效花费更少的指令去完成一项操作。由于 Java 要支持跨平台所以 Java 的指令都是针对栈来设计的。不同平台 CPU 架构不同所以不能设计成基于寄存器的。优点是跨平台指令集小编译器容易实现缺点是性能下降实现同样的功能需要更多的指令。
二、JVM 的组成部分 1. Class Loader类加载器
类加载器是 Java 运行时环境JRE的一部分负责在运行时动态地加载 Java 类到 Java 虚拟机JVM中。其主要作用包括加载类、链接类以及初始化类。具体来说它会根据类的全名找到对应的.class文件并将其加载到 JVM 中然后进行链接操作包括验证、准备和解析最后为类的静态变量赋予正确的初始值完成初始化。
Java 中有三种主要的类加载器启动类加载器负责加载 Java 的核心类库扩展类加载器负责加载 Java 的扩展类库系统类加载器负责加载应用程序的类路径下的所有类。此外开发者还可以自定义类加载器以满足特殊需求如热部署、代码加密等。
Java 的类加载器采用双亲委派模型当一个类加载器收到类加载请求时它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器去完成。这种模型保证了 Java 核心类库的类型安全避免了类的重复加载并且使得 Java 应用更加稳定。
类加载器的隔离性确保了不同应用程序或库之间的类不会相互干扰从而避免了潜在的类冲突和不安全行为。例如两个不同的应用程序可能都使用了一个名为com.example.Utils的类但这两个类实际上可能是完全不同的。通过为每个应用程序使用不同的类加载器可以确保每个应用程序加载和使用它自己的com.example.Utils类版本而不会与其他应用程序的类发生冲突。
自定义类加载器允许开发者扩展 Java 的类加载机制以满足特定的需求。通过继承ClassLoader类并重写其中的方法开发者可以控制类的加载过程实现如加密类的加载、从特定位置如数据库或网络加载类等高级功能。
2. Execution Engine执行引擎
执行引擎是 Java 虚拟机核心的组成部分之一它的任务是将字节码指令解释或编译为对应平台上的本地机器指令才可以执行。可以把执行引擎看作是将高级语言翻译为机器语言的译者。
执行引擎在执行的过程中其行为有解释执行和编译执行两种。解释执行是当 Java 虚拟机启动时根据预定义的规范对字节码采用逐行解释的方式执行将每条字节码文件中的内容 “翻译” 为对应平台的本地机器指令执行。而编译执行主要是通过 JITJust In Time Compiler编译器实现虚拟机将源代码一次性直接编译成和本地机器平台相关的机器语言但并不是马上执行。
Java 属于半编译半解释型语言JDK1.0 时代Java 语言定位为 “解释执行”后来发展出可以直接生成本地代码的编译器。现在 JVM 在执行 Java 代码的时候通常都会将解释执行与编译执行二者结合起来进行。JIT 编译器将字节码翻译成本地代码后可以做一个缓存操作存储在方法区的 JIT 代码缓存中并且在翻译成本地代码的过程中可以做优化。
3. Native Interface本地接口
本地接口是 Java 虚拟机JVM提供给开发者的一种机制用于在 Java 程序中调用本地方法。本地接口允许 Java 代码与底层的本地代码进行交互使得 Java 程序可以调用 C、C 等本地语言编写的函数库或操作系统提供的功能。
本地接口的作用是融合不同的编程语言为 Java 所用它的初衷是融合 C/C 程序。本地方法栈中登记 native 方法在执行引擎执行时加载本地方法库。有声明无实现。
本地接口允许 Java 程序调用本地方法从而实现与底层本地代码的交互。Java 程序可以通过本地接口调用本地方法进而调用本地函数库提供的功能如操作系统的系统调用、硬件设备的访问等。本地接口为 Java 程序提供了与底层系统的无缝连接极大地扩展了 Java 的应用范围。
4. Runtime data area运行数据区
运行时数据区是整个 JVM 的重点所有写的程序都被加载到这里之后才开始运行。它是 JVM 在执行 Java 程序时用于存储和管理各种数据的内存区域包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等五大区域。
方法区存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据在 JDK 1.8 及以后版本中称为元空间。堆是所有线程共享的一块内存区域主要用于分配和回收 Java 对象实例几乎所有的对象都在堆上分配内存并且是垃圾回收的主要区域。虚拟机栈是每个线程私有的用于存储方法调用和局部变量描述的是 Java 方法执行的内存模型。本地方法栈与虚拟机栈的作用类似是为虚拟机调用 Native 方法服务的。程序计数器是当前线程所执行的字节码的行号指示器是线程私有的用于记录当前线程执行的字节码指令地址是唯一一个不会出现内存溢出错误的区域。
三、JVM 的内存管理 JVM 的内存管理是其核心功能之一它直接影响着 Java 程序的性能和稳定性。以下将详细介绍 JVM 的内存管理机制。
1. 内存区域划分
内存区域内存区域描述是否线程私有主要存储内容程序计数器当前线程所执行的字节码的行号指示器JVM 中唯一不会出现内存溢出错误的区域是作为行号指示器独立存储各线程执行位置互不影响虚拟机栈每个方法被执行时 Java 虚拟机同步创建栈帧包含局部变量表、操作数栈等信息可能抛出 StackOverflowError 和 OutOfMemoryError是局部变量表存放方法中的局部变量操作数栈用于字节码执行时的运算动态链接用于在运行时找到被调用方法的实际地址方法出口表示方法该如何结束本地方法栈作用与虚拟机栈类似为 Native 方法服务可能抛出 StackOverflowError 和 OutOfMemoryError是堆整个 Java 应用程序共享的区域用于存放和管理对象和数组是垃圾回收的主要区域可动态扩展扩展失败抛出 OutOfMemoryError 异常采用分代收集算法分为新生代和老年代等区域否对象、数组方法区JDK 1.8 后为元空间存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据垃圾回收主要目标是对常量池回收和类卸载较难实现动态扩展失败抛出 OutOfMemoryError 异常运行时常量池是其一部分用于存放编译器生成的各种字面量和符号引用否类信息、常量、静态变量、即时编译器编译后的代码
2. 内存分配
对象在堆中的分配当一个对象被创建时它首先进入新生代的 Eden 区。如果 Eden 区满了就会触发 Minor GC将 Eden 区中仍然存活的对象复制到 Survivor 区中。如果 Survivor 区也满了或者对象在经过多次 Minor GC 后仍然存活就会被转移到老年代中。栈内存分配Java 栈的分配是和线程绑定在一起的当创建一个线程时JVM 就会为这个线程创建一个新的 Java 栈。一个线程的方法的调用和返回对应着这个 Java 栈的压栈和出栈。栈中主要存放一些基本的数据类型和对象句柄引用。局部变量表中存储着方法相关的局部变量其内存空间可以在编译期间就确定运行时不再改变。直接内存分配NIO 使用 java.nio.ByteBuffer.allocateDirect () 方法分配内存是本机内存而不是 Java 堆上的内存增加了一次系统调用。直接 ByteBuffer 产生的数据和网络或者磁盘交互都在操作系统的内核空间中发生不需要将数据复制到 Java 内存中加快数据处理速度。
3. 内存分配与回收策略
分代收集器对象优先在 Eden 分配新创建的对象首先在新生代的 Eden 区分配内存。大对象直接进入老年代可以通过参数调配使得大对象直接进入老年代避免在新生代频繁进行垃圾回收。长期存活的对象将进入老年代对象在新生代经过多次垃圾回收后仍然存活会进入老年代。Minor GC 与对象年龄每次对新生代的 GCMinor GC存活的对象通过标记 - 复制算法复制到 Survivor 空间并且年龄 1如果年龄达到老年代要求动态年龄算法则进入老年代。如果 Survivor 空间无法存放存活的对象则根据分配担保机制进入其他内存空间实际上大多是实现都是进入老年代。空间分配担保在 Minor GC 之前检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果大于就能保证这次 Minor GC 是安全的如果小于则查看 -XX:HandlePromotionFailure 参数是否允许担保失败。如果允许继续检查老年代最大可用空间是否大于历次晋升到老年代对象的平均大小大于则尝试进行一次 Minor GC分配失败再进行一次 Full GC小于则进行一次 Full GC。如果不允许担保失败直接进行一次 Full GC。如果分配失败再进行一次 Full GC。
四、垃圾收集器 1. 对象失效判定算法
对象失效判定是垃圾回收的关键步骤之一主要包括引用计数算法和可达性分析算法以及不同引用类型对象的回收方式。
引用计数算法每有一处引用引用计数器加 1每有一处引用失效引用计数器减 1引用计数器归 0则该对象可以被回收。这种算法简单高效但面对复杂场景程序需要额外的工作以保证算法有效例如解决循环引用的问题。如以下代码所示
public class ObjA {private Object instance;public void setInstance(Object instance) {this.instance instance;}
}
public class ObjB {private Object instance;public void setInstance(Object instance) {this.instance instance;}
}
public class Test {public static void main(String[] args) {ObjA obja new ObjA();ObjB objb new ObjB();obja.setInstance(objb);objb.setInstance(obja);obja null;objb null;//循环引用如果使用引用计算算法则两个对象的引用计算器都为1都将无法被gc回收。//而实际上两个对象已经没有其他引用了永远也不会被程序调用。}
}可达性分析算法通过 GC_Roots 对象向下搜索引用形成引用链如果对象没有与任何引用链相连该对象可被回收。可以作为 GC_Roots 的对象类型有静态属性、字符串常量、被 Native 方法引用的对象、Java 虚拟机内部的引用、同步锁持有的对象、反映 Java 虚拟机内部情况的 JMXBean 等。
不同引用类型对象的回收
Strongly Reference强引用所有基本对象默认为强引用强引用只要 GCRoots 可达即不能被回收。Soft Reference软引用被 java.lang.ref.SoftRefenrence 对象包装的对象即为软引用对象当因为 JVM 堆内存空间不足时触发的 gc 会将软引用包装的对象回收。Weak Reference弱引用被 java.lang.ref.WeakRefenrence 对象包装的对象即为弱引用对象在每一次 gc 行为发生时都将被回收。Phantom Reference虚引用被 java.lang.ref.PhantomReference 包装的对象即为虚引用对象在构造时需要传入一个 ReferenceQueue 队列对象可以通过该对象在对象被回收时通知系统。虚引用的 get 方法返回 null不能通过虚引用获取其包装的对象虚引用唯一的作用是用于对象回收的系统通知。虚引用在每一次 gc 行为发生时都将被回收。
如果在构造 SoftRefenrence/WeakRefenrence/PhantomReference 时传入一个引用队列参数对象将被回收时都将存入该队列中。可以通过如下编码进行通知
ReferenceQueueRefObject queue new ReferenceQueue();
new Thread(() - {while (true) {Reference? extends RefObject reference;if ((reference queue.poll())! null) {//需要注意此时通过reference.get()返回的都将为null//也就是说此时对象已经被回收了System.out.println(对象是否被回收 reference.get() null);System.out.printf(引用: %s 将被回收\n, reference);//DOING SOMETHING}}
}, monitorGcThread).start();2. 垃圾收集算法
垃圾收集算法主要有标记 - 清除算法、标记 - 复制算法、标记 - 整理算法等以及分代收集理论。
分代收集理论基础假说
弱分代假说绝大多数对象都是朝生夕灭的。强分代假说熬过越多次垃圾收集过程的对象就越难以消亡。跨代引用假说跨代引用相对于同代引用来说仅占极少数。
基于这些假说垃圾收集器设计原则是将 Java 堆分出不同的区域然后将回收对象依据其经历垃圾收集过程的次数高低分配到不同的区域之中存储。
标记 - 清除算法标记清除算法是大部分垃圾回收算法的基础。首先标记出所有需要回收的对象然后回收所有被标记的对象。主要缺点是执行效率不稳定Java 堆中有太多对象时执行效率越差还会产生大量不连续的内存碎片。 标记 - 复制算法适用于对象存活率较低的垃圾回收如新生代。将内存分为两半每次只使用其中一半。当使用中的一半内存耗尽进行空间清理将存活的对象复制到另一半清除使用中的一半内存然后启用另一半内存。但该算法内存利用率仅为 50%如果内存中的多数对象都是存活的需要耗费大量的内存间复制的开销。优化后的算法将内存空间分为一个 Eden 空间和两个 Survivor 空间提高了内存利用率。 标记 - 整理算法适用于对象存活率较高时的垃圾回收如老年代。标记要清理或不要清理的对象将所有存活的对象都向内存空间一端移动然后清理掉边界以外的内存。该算法的问题是移动存活对象并更新引用内存地址带来大量消耗且程序需要等待这些工作完成才能正常执行不移动对象的话会导致大量内存碎片使得内存分配非常复杂。 3. 经典垃圾收集器
经典垃圾收集器包括 Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、Garbage First 等收集器它们各有特点和工作流程。
Seria 垃圾收集器最基础、历史最悠久的新生代垃圾收集器单线程工作简单高效但存在工作线程停顿问题是 HotSpot 虚拟机客户端模式下的默认新生代收集器。
ParNew 垃圾收集器Serial 垃圾收集器的多线程并行版本激活 CMS 后默认的新生代收集器在多逻辑核心环境下相对 Serial 稍微高效。
Parallel Scavenge 垃圾收集器基于标记 - 复制算法实现的多线程并行收集的新生代垃圾收集器高吞吐量适合后台运算而不需要太多交互的分析任务。
Serial Old 垃圾收集器Serial 垃圾收集器的老年代版本单线程收集器基于标记 - 整理算法。
Parallel Old 垃圾收集器Parallel Scavenge 收集器的老年代版本支持多线程并发收集基于标记 - 整理算法。
CMS (Concurrent Mark Sweep) 收集器以获取最短回收停顿时间为目标的收集器基于标记 - 清除算法实现增量更新解决并发收集问题。但存在对处理器资源敏感、产生浮动垃圾、导致内存碎片等问题。GC 过程包括初始标记stop the world、并发标记、重新标记stop the world、并发清除。
Garbage First (G1) 垃圾收集器主要面向服务端应用可控的停顿时间整理上是基于标记 - 整理算法局部是基于标记 - 复制算法是当前服务端模式下的默认垃圾收集器。原始快照解决并发收集问题。G1 收集器将连续的 Java 堆分为多个大小相等的独立区域各个区域可以独立扮演 EdenSurvivor 空间还有特殊的 Humongous 区域专门存储大对象。GC 过程包括初始标记stop the world、并发标记、重新标记stop the world、并发清除。
4. 低延迟垃圾收集器
Shenandoah 和 ZGC 收集器是低延迟垃圾收集器特点和工作流程如下
Shenandoah 垃圾收集器非 OracleOracle JDK12 不支持。并发垃圾标记 并发对象清理后的整理 基本全程并发 GC 10mills是 G1 的继承者。改进了记忆集采用链接矩阵代替非分代收集。GC 过程包括初始标记短暂停顿、并发标记、最终标记短暂停顿、并发清理、并发回收、初始引用更新短暂停顿、并发引用更新、最终引用更新短暂停顿、并发清理。通过读屏障和 “Brooks Pointers” 解决与用户线程的并发问题。对象头部存储一个内存地址指针正常时指向自己当对象移动时旧对象的地址指向新的对象内存空间实现间接访问对象。旧对象需要在完成引用更新后再进行删除访问内存地址指针必须是同步操作以防止多线程内存不一致问题Shenandoah 收集器通过 CAS 来保证并发访问。
ZGC 收集器since JDK11 Oracle基于 Region 内存布局不设分代使用读屏障、染色指针和内存多重映射等技术实现可并发的标记 - 整理算法以低延迟为首要目标。动态创建和销毁的动态容量大小的 Region包括小型 Region容量固定为 2MB存放小于 256KB 的小对象、中型 Region容量固定为 32MB用于放置大于等于 256KB 但小于 4MB 的对象、大型 Region容量不固定可以动态变化用于放置大于 4MB 的大对象每个大型 Region 只会存放一个大对象。GC 过程包括初始标记短暂停顿、并发标记、最终标记短暂停顿、并发预备重分配、并发重分配、并发重映射。染色指针在对象的内存地址上占用 4 个字节来判断对象是否被移动过这也导致了 ZGC 能够管理的内存不能超过 4TB不能支持 32 位平台不能支持压缩指针。ZGC 在传输信息时会产生大量的 NAKACK 对象并且有重发机制在确定消息送达之前数据会滞留在内存中。由于共享数据需要与多个节点通信网络资源紧张导致大量的消息数据滞留在内存中。很快产生了内存溢出。解决方案是替换全局缓存避免频繁的写操作也避免了较多的缓存同步通信。ZGC 的劣势是没有分代收集导致在 GC 过程中产生大量新对象的场景将导致大量浮动垃圾清理速度慢于对象产生速度的话将可能导致这种情况恶化。
五、虚拟机性能监控、故障处理工具 1. 基础故障处理工具
包括 jps、jstat、jinfo、jmap、jhat、jstack 等工具的功能和使用方法。
jps虚拟机进程状况工具
功能 列出正在运行的虚拟机进程。显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID。 使用频率最高的 JDK 命令行工具。命令格式jps [options] [hostid]。 options -q只输出 LVMID虚拟机本地 ID省略主类的名称。-m输出虚拟机进程启动时传递给主类 main 函数的参数。-l输出主类的全名如果进程执行的是 JAR 包则输出 JAR 路径。-v输出虚拟机进程启动时的 JVM 参数。 hostid通过 RMI 协议查看在 RMI 注册表中的远程主机的进程信息等。
jstat虚拟机统计信息监视工具
功能 显示本地或与远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据。 纯文本控制台环境的 JVM 定位虚拟机性能问题的常用工具。命令格式jstat [option vmid [interval [s|ms] [count]] ]。 options不同的选项可以展示不同的运行时数据。vmid虚拟机 id本地进程与 lvmid 一致远程格式如下[protocol:][//] vmid [hostname [:port]/servername]。interval查询间隔单位毫秒。count查询次数。
jinfoJava 配置信息工具
功能 查看 Java 虚拟机运行时的参数配置。运行时修改一部分支持修改的虚拟机参数值。 命令格式jinfo [option] pid。 option -flag查询参数。-sysprops查看 System.getProperties ()。
jmapJava 内存映像工具
功能 生成堆转储快照一般称为 heapdump 或者 dump 文件。查询 finalize 执行队列、Java 堆和方法区的详细信息空间使用率当前用的哪种收集器等。 堆快照堆中的对象信息实例数量堆中的内存占用信息等信息。命令格式jmap [option] vmid。 option不同的选项可以实现不同的功能。
jhat虚拟机堆转储快照分析工具
与 jmap 搭配使用。功能分析 jmap 生成的堆转储快照。不推荐使用可以用其他方式分析堆转储快照 在服务器上使用 jhat 会比较占用服务器资源。jhat 的分析功能比较简陋。 推荐使用 VisualVM。Eclipse Memory Analyzer。IBM HeapAnalyzer。
jstackJava 堆栈跟踪工具
功能 用于生成虚拟机当前时刻的线程快照threaddump/javacore 文件。通常用于定位线程出现长时间停顿的原因如线程死锁、死循环、请求外部资源长时间挂起等。 线程快照当前虚拟机每一条线程正在执行的方法堆栈的集合。命令格式jstack [option] vmid。 option不同的选项可以实现不同的功能。 替代方案 自 JDK5 起Thread 类提供了 getAllStackTraces () 方法用于获取虚拟机所有线程的 StackTraceElement 对象用这个方法可以完成 jstack 的大部分功能。用 Thread.getAllStackTraces () 方法做一个 web 页面用于开发时查看线程堆栈信息。
2. 可视化故障处理工具
如 JHSDB、JConsole、VisualVM、JFR、JMS 等工具的功能和特点。
JHSDB基于服务性处理的调试工具
集成式的可视化多功能工具箱集成了多个 jdk 命令行工具且功能更强大。命令行模式下的使用可以通过特定的命令行参数进行操作。基于服务性代理 (Seriviceability Agent, SA) 实现的进程外调试工具。对压缩指针的支持存在缺陷64 位系统测试时可以禁用压缩指针-XX:-UseCompressedOops。
JConsoleJava 监视与管理控制台
Java Monitoring and Management Console。基于 JMX (Java Management Extensssions) 的可视化监视、管理工具。功能 收集系统运行信息 内存。线程线程运行情况、死锁。类创建统计。CPU。 动态调整参数。
VisualVM多合一故障处理工具
All-in-One Java Troubleshooting Tool。Java 功能最强大的运行监视和故障处理程序之一。基于 NetBeans 平台开发工具可安装插件。功能 显示虚拟机进程以及进程的配置、环境信息类似 jps、jinfo。监视应用程序的处理器、垃圾收集、堆、方法区以及线程的信息类似 jstat、jstack。dump 以及分析堆转储快照类似 jmap、jhat。方法级的程序运行性能分析找出被调用最多、运行时间最长的方法。离线程序快照运行程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照可以将快照发送开发者进行 bug 反馈。其他插件功能。 JDK6 Update7 发布向下兼容至 JDK1.4.2 版本。**JDK14 起 JDK 不再提供 visualvm需手动下载安装 **https://visualvm.github.io/。 修改 visualvm.conf 设置 jdk 路径。打开 visualvm.exe。 插件安装 JDK_HOME/bin 目录下找到 visualvm。手动安装 插件网站https://visualvm.github.io/pluginscenters.html。下载 nbm 插件包。点击工具 插件 已下载菜单指定 nbm 包安装。插件安装目录jdk9 jkd_HOME/lib/visualvm 目录下。 自动安装 点击工具 插件菜单。选择可用插件及已安装勾选对应插件安装或激活即可。 Mac 下使用 visualvm https://visualvm.github.io/下载 visualvm安装后即可使用。集成 idea 插件中心下载 visualvm launcher。 插件推荐列表 Btrace动态加入原本不存在的调试代码 https://github.com/braceio/btrace。 打印调用堆栈、参数、返回值。进行性能监视、定位链接泄漏、内存泄漏。解决多线程竞争问题等。
JFR可持续收集数据的飞行记录仪
Java Flight Recorder。内建在 HotSpot 虚拟机内的信息收集框架。企业版收费个人版免费。对生产环境的网站吞吐量影响小于 1% – Zero Performance Overhead。动态开启停止。飞行记录指程序运行一段时间内的信息记录 一般信息关于虚拟机、操作系统和记录的一般信息。内存关于内存管理和垃圾收集的信息。代码关于方法、异常错误、编译和类加载的信息。线程关于应用程序中线程和锁的信息。I/O关于文件和套接字输入、输出的信息。系统关于正在运行 Java 虚拟机的系统、进程和环境变量的信息。事件关于记录中的事件类型的信息可以根据线程或堆栈跟踪按照日志或图形的格式查看。 JFR 获取的数据粒度较细也较为可靠。可以使用 JMS 进行统计分析。
JMS可持续在线的 JVM 监控工具
Java Mission Control。监控 Java 虚拟机。企业版收费个人版免费。基于 Eclipse RCP 框架。采取 JMX 协议通信。主要功能 显示来自虚拟机 MBean 提供的数据。展示 JFR 的数据并分析。
六、调优案例分析与实战 1. 大内存硬件上的程序部署策略
在大内存硬件环境下程序部署可能会出现一些问题。以一个 15 万 PV / 日左右的在线文档类型网站为例该网站服务器硬件为四路志强处理器、16GB 物理内存操作系统为 64 位 CentOS5.4Resin 作为 Web 服务器软件版本选用 64 位的 JDK5管理员启用一个虚拟机实例将 Java 堆大小固定在 12GB。然而运行效果并不理想网站经常不定期出现长时间失去响应。
问题剖析
这种情况是由垃圾收集器停顿所导致的。默认使用的是吞吐量优先收集器回收 12GB 的 Java 堆一次 Full GC 的停顿时间高达 14 秒。此外由于程序设计原因访问文档时会把文档从磁盘提取到内存中导致内存中出现很多由文档序列化产生的大对象这些大对象大多在分配时就直接进入了老年代没有在 Minor GC 中被清理掉使得内存很快被消耗殆尽。
解决方案
目前在大内存硬件上主要有两种程序部署方式。
方式一通过一个单独的 Java 虚拟机实例来管理大量的 Java 堆内存。这种方式需要考虑一些问题如回收大块堆内存而导致的长时间停顿虽然 G1 收集器出现后有了增量回收但要到 ZGC 和 Shenandoah 收集器成熟后才相对彻底解决大内存必须有 64 位虚拟机支持但由于压缩指针、处理器缓存行容量等因素64 位性能普遍低于 32 位虚拟机必须保证应用程序足够稳定因为这种大型单体应用要是发生了堆内存溢出几乎无法产生堆转储快照就算成功了也难以分析相同的程序在 64 位消耗的内存一般比 32 位虚拟机要大可以开启默认开启压缩指针来缓解。方式二同时使用若干个虚拟机建立逻辑集群来利用硬件资源。具体做法是在一台机器上启动多个应用服务器进程为每个服务器进程分配不同端口然后在前端建立一个负载均衡器以反向代理的方式来分配访问请求。这种方式可能会遇到一些问题如节点竞争全局资源很难最高效率地利用某些连接池32 位虚拟机作为集群节点会受到内存限制大量使用本地缓存的应用在集群中会造成较大内存浪费等。
对于该案例最终方案是调整为建立 5 个 32 位 JDK 的逻辑集群每个进程按 2GB 计算占用 10GB。另外建立一个 Apache 服务作为前端均衡代理考虑到用户对响应速度比较关心并且文档服务的主要压力集中在磁盘和内存访问处理器资源敏感度低因此改为 CMS 收集器进行垃圾回收。
2. 集群间同步导致的内存溢出
以一个基于 B/S 的 MIS 系统为例该系统由两台双路处理器、8GB 内存的惠普小型机组成使用 WebLogic 部署每台机器有 3 个 WebLogic 实例构成一个 6 节点亲和式集群。节点之间没有共享 Session但有一些需求要实现部分数据在各个节点间共享一开始放在数据库后因读写频繁影响性能。之后构建了一个全局 JBossCache 缓存启用后不定期出现多次内存溢出问题。
问题剖析
由于 JBossCache 在传输信息时会产生大量的 NAKACK 对象并且有重发机制在确定消息送达之前数据会滞留在内存中。由于共享数据需要与多个节点通信网络资源紧张导致大量的消息数据滞留在内存中很快产生了内存溢出。
解决方案
替换全局缓存避免频繁的写操作也避免了较多的缓存同步通信。
3. 堆外内存导致的溢出错误
以一个基于 B/S 的电子考试系统为例测试期间发现服务端不定时抛出内存溢出异常。尝试把堆内存调到最大基本没效果加入 -XX:HeapDumpOnOutOfMemoryError 参数也没有任何反应。只好挂着 jstat 观察发现垃圾并不频繁内存很稳定但就是照样不停抛出 OOM。
问题剖析
Direct Memory 耗用的内存不算入