JVM性能优化专题

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
应用实时监控服务-用户体验监控,每月100OCU免费额度
可观测可视化 Grafana 版,10个用户账号 1个月
简介: JVM性能优化专题

JVM加载Class文件的原理机制

  1. 编译阶段,Java源文件被编译成.class文件,包含字节码和元数据。
  2. 类加载阶段,.class文件由类加载器加载进JVM。类加载器通常采用双亲委派模型,首先请求父类加载器加载,如果父类加载器无法加载则自己加载。
  3. 链接阶段,将类的二进制数据合并到JVM运行状态之中的过程。包含验证、准备和解析三个步骤。
  • 验证:确保加载的类满足JVM规范,没有安全方面的问题。
  • 准备:为类的静态变量分配内存,并设置默认初始化值。
  • 解析:将类中的符号引用替换为直接引用。
  1. 初始化阶段,如果该类具有超类,则对其超类进行初始化。然后执行()方法,完成类的静态变量初始化。
  2. 使用阶段,实例化对象和调用方法等,Class对象在JVM中一直有效,直到JVM退出。
  3. 卸载阶段,不会主动卸载Class对象。但是如果一个类的所有实例都被回收,对应的Class对象有可能被卸载。

这个加载过程可以保证Class文件被正确加载到JVM,并且在加载过程中做必要的校验和转换,为后续使用阶段做好准备工作。整个加载过程遵循双亲委派模型,先让父类加载器试图加载,父类加载器无法加载时子加载器才会尝试加载。

每个加载的Class在JVM中都有一个对应的Class对象,存储了类的结构信息,方法,变量等数据。这个Class对象一直存在于JVM中,为后续的实例化,反射等提供支持。

GC是什么?为什么要有GC?

GC是Garbage Collection的简称,即垃圾收集。它是Java内存管理的一大特点。

为什么需要GC:

  1. 程序运行时会不断地生成新的对象,这些对象都需要申请内存空间。如果对象无法被回收,将导致内存占用过高,甚至OOM。
  2. 有些对象在使用完成后,并不再被引用,但其占用的内存空间无法自动释放。这些无法访问的对象就是"垃圾",需要手动回收其占用的内存空间。
  3. 手动回收无法访问的对象非常困难和低效。每个对象需要人工判断是否还可访问,然后决定是否回收内存。这几乎是不可能完成的任务。

所以,Java引入了GC来自动管理内存,回收无法访问的对象所占用的内存空间。它可以自动判断哪些对象不可访问,并回收其内存,从而解决手动内存管理的问题,防止内存泄漏。

GC的工作原理:

  1. GC会自动监控对象的引用关系,当一个对象只被不可访问的对象引用时,它也会成为不可访问的"垃圾"对象。
  2. 垃圾收集器会在内存即将用尽或JVM空闲时,自动运行,回收不可访问对象的内存空间。
  3. 通常使用引用计数或者可达性分析等算法来判断对象是否可访问。如果一个对象可以从GC Roots节点可达,那么它就是可访问的。
  4. 常见的GC算法有标记清除、标记压缩、分代收集等。不同的算法适用于不同的场景。

GC就是JVM自动内存管理的一种方式,它可以自动回收不可访问对象占用的内存空间,从而防止内存泄漏,保证程序的正常运行。

Java垃圾回收机制

  1. GC Roots:包括活动线程栈(栈帧中的局部变量)、方法区中类静态属性引用的对象、方法区中常量引用的对象等。
  2. 可达性分析:从GC Roots出发向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则该对象是不可用的。
  3. 垃圾收集算法:
  • 标记清除:标记出不可达对象,然后统一回收。会产生大量内存碎片。
  • 标记压缩:标记不可达对象,然后移动可达对象,最后清理掉边界以外的内存空间。可以减少内存碎片。
  • 分代收集:将内存分为新生代和老年代,新生代采用复制算法,老年代采用标记清除/压缩算法。新生代对象容易回收,老年代对象存活时间长。
  1. 垃圾收集器:
  • Serial:单线程收集器,STW(Stop The World)时间长,但简单高效。适用于小内存和单CPU环境。
  • Parallel:多线程收集器,STW时间短,CPU密集型,适用于中等并发的服务器环境。
  • CMS:并发收集器,心臟不停跳。并发收集消度高但碎片多,适用于快速响应的环境。
  • G1:分代收集器,并行和并发相结合,整理碎片和回收效率高,适用于大内存和并发环境。
  1. 垃圾收集调优:根据应用的特点选择合适的垃圾收集器和相应的参数配置,达到一个短GC停顿时间和高吞吐量的平衡。

概括来说,Java垃圾回收机制是通过GC Roots与对象之间的可达性分析来判断对象是否存活,然后采用不同的垃圾收集算法和收集器来回收不可用对象的内存,实现自动内存管理。

如何判断一个对象是否存活?(或者GC对象的判定方法)

在Java中,对象是否存活主要通过可达性分析来判断。可达性分析的基本思想是:

如果一个对象可以被GC Roots直接或间接引用到,则该对象是存活的。

如果一个对象无法被GC Roots可达,则该对象是不可用的,可以被回收。

GC Roots包括:

  • 虚拟机栈(栈帧中的局部变量区)中的引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(Native方法)引用的对象。

那么,判断一个对象是否存活的基本步骤如下:

  1. 找出所有的GC Roots对象。这些对象是存活的,并作为开始搜索的起点。
  2. 从GC Roots开始向下搜索,如果一个对象与GC Roots之间有直接或间接的引用关系,那么这个对象也是存活的。
  3. 重复步骤2,直到没有更多可达的对象。可达的对象都属于存活对象。
  4. 没有被搜索到,或者无法从GC Roots开始搜索到的对象就是不可用对象,可以被垃圾收集器回收。
  5. 垃圾收集器通过搜索GC Roots开始的对象网,把所有可达对象都标记为存活,然后清理掉那些没有标记的对象。

举个例子:

java

obj1 → obj2 → obj3 → obj4

  • 如果obj1被GC Roots引用,那么obj1、obj2、obj3和obj4都是存活的。
  • 如果只有obj3被GC Roots引用,那么只有obj3和obj4是存活的,obj1和obj2会被回收。
  • 如果整个对象网中没有一个对象被GC Roots引用,那么所有对象都会被回收。

所以,判断对象是否存活,实际上就是判断该对象是否能被GC Roots直接或间接可达到。如果一个对象与GC Roots之间完全没有引用链相连,那么该对象将会被垃圾收集器回收。

垃圾回收的优点和原理

  1. 自动内存管理:程序员不需要手动释放内存,GC可以自动回收不再使用的对象,简化编程难度。
  2. 避免内存泄漏:忘记手动释放内存会导致内存泄漏,GC可以自动释放不可达对象,避免内存泄漏。
  3. 分配内存效率更高:GC自动管理内存,通过复用回收得来的内存,分配内存时无需从操作系统获取,更加高效。

垃圾回收的基本原理:

  1. 找出GC Roots:栈帧中的局部变量、静态属性、常量等。
  2. 从GC Roots开始向下搜索,找到所有可达对象。可达对象就是存活对象。
  3. 搜索完成后,剩下的对象就是不可达对象,可以被回收。
  4. 垃圾收集器采用不同算法对内存进行回收,常见算法有:- 标记清除:标记出不可达对象,统一回收。会产生大量内存碎片。
  • 标记压缩:标记不可达对象,移动可达对象,回收碎片内存。减少内存碎片。
  • 分代回收:新生代使用复制算法,老年代使用标记清除/压缩算法。

考虑2种回收机制:

  1. 标记清除:简单,运行速度快,但会产生大量内存碎片。适用于新生代。

public void gc() {
    mark(); // 标记所有可达对象
    sweep(); // 清除所有未标记对象
}
  1. 标记压缩:需要付出额外代价移动内存,但可以减少内存碎片。适用于老年代。

public void gc() {
    mark();    // 标记所有可达对象
    compact(); // 移动可达对象,回收内存碎片
} 

标记清除通过直接释放不可达对象来回收内存,速度快但会有大量内存碎片。标记压缩需要移动可达对象,额外付出性能代价,但可以减少内存碎片。

垃圾回收器的基本原理是什么?垃圾回收器可以马上回啊收内存吗?有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器的基本原理:

  1. 找出GC Roots:栈帧中的局部变量、静态属性、常量等。
  2. 从GC Roots开始向下搜索,找到所有可达对象。可达对象就是存活对象。
  3. 搜索完成后,剩下的对象就是不可达对象,可以被回收。
  4. 垃圾收集器采用不同算法对内存进行回收,常见算法有:标记清除、标记压缩、分代回收等。

垃圾回收器不能马上回收内存,主要有以下原因:

  1. 垃圾收集需要付出较高的性能开销,频繁回收会严重影响程序性能。
  2. 回收内存需要STW(Stop The World),停止用户线程,如果回收太频繁会影响程序响应性。
  3. 部分垃圾收集器(CMS)是并发的,需要等待用户线程释放对象引用,否则会导致既存的可达对象被错误回收。

主动通知虚拟机进行垃圾回收的方法:

  1. System.gc():建议JVM进行垃圾回收。JVM会根据系统运行情况自行决定是否执行回收。
  2. Runtime.getRuntime().gc():同上,尝试建议JVM进行回收。
  3. Xms和Xmx:可以设置初始大小和最大大小来产生内存溢出,触发full gc。但这种方式不可靠,不推荐。
  4. Profile触发:通过开启GC Profiling,让Profile决定何时触发Young/Full GC。但这也依赖于运行环境,不可靠。
  5. 调整垃圾收集器及内存参数:可以通过-XX选项设置垃圾收集器类型和内存参数来间接影响GC频率。这是比较可靠的方式之一。

-XX:+UseParallelGC # 设置为并行收集器

-XX:MinHeapFreeRatio=40 # 最小空闲比例触发YGC

-XX:MaxHeapFreeRatio=70 # 最大空闲比例触发Full GC

所以,总结来说,虚拟机会根据系统运行情况决定何时进行垃圾回收,我们无法完全主动控制GC的时机。但是,我们可以通过调整JVM内存参数或垃圾收集器类型等方式间接地影响垃圾回收的频率,在一定程度上主动通知虚拟机进行垃圾回收。

Java中会存在内存泄漏吗

Java中由于有垃圾回收机制,可以减少很大一部分内存泄漏的可能,但是仍存在一些场景会产生内存泄漏:

  1. 忘记释放非GC管理的内存:像NIO的DirectByteBuffer等需要手动释放,忘记释放会内存泄漏。
  2. 静态引用的对象:如果一个对象的生命周期很长,但只有静态引用,容易内存泄漏。因为静态引用的生命周期跟类一样长。
  3. 发生在线程或线程池使用过程中:如果线程池中某个线程一直持有一个对象的引用,而这个对象很大,容易内存泄漏。
  4. 网络连接或数据库连接没有正确关闭:这些连接如果长期未关闭,会持有大量资源导致内存泄漏。
  5. 监听器使用不当:如果监听器注册后没有正确unregister,会对资源持有引用而内存泄漏。
  6. 映射使用不当:像Map等映射如果keyDisappear后value没有释放,会产生内存泄漏。

解决办法:

  1. 及时关闭外部资源:如数据库连接、网络连接、文件流等。
  2. 尽量不要使用静态引用持有大对象。如果必须使用,应及时设置为null释放。
  3. ThreadLocal及时remove:如果线程结束,应移除ThreadLocal中的引用以释放内存。
  4. 注册监听器后及时unregister:监听器不再使用应立即unregister以释放资源。
  5. 使用WeakReference或SoftReference:如果对象生命周期不确定,可以使用弱引用或软引用封装,避免内存泄漏。
  6. 映射中使用WeakHashMap:如果key可能消失,可以使用WeakHashMap,其entry会自动gc,防止内存泄漏。

7.及时显式调用资源的close方法:像NIO的DirectByteBuffer就需要手动调用free释放内存。

  1. 避免高强度的对象层级:对象的层级关系太深,层与层之间相互引用,使得整体对象链条难以被回收,容易发生内存泄漏。

所以,总结来说,虽然Java的GC可以减少大部分内存泄漏,但是仍需程序员在设计和编码时多加留意,及时释放无用资源,避免产生难以被GC的对象,从而彻底解决内存泄漏问题。良好的编码习惯可以极大的减少内存泄漏

目录
相关文章
|
分布式计算 Java 开发者
GitHub爆款!Java性能优化:轻松道破软件性能调优,不止搞定JVM
今天给大家带来的是:周明耀老师的 《大话Java性能优化:轻松道破软件性能调优方法论和具体实现路径》,全面细致,一本书搞定性能优化 周明耀是谁? 12年投资银行项目、分布式计算项目工作经验,IBM开发者论坛专家作者。一名IT技术狂热爱好者,一名顽强到底的工程师。推崇技术创新、思维创新,对于新技术非常的热爱,致力于技术研发、研究,通过发布文章、书籍、互动活动的形式积极推广软件技术。欢迎添加作者“michael_tec”,共同探讨IT技术话题。
|
3月前
|
存储 算法 Java
jvm性能优化(一)-基于JDK1.8
jvm性能优化(一)-基于JDK1.8
|
1月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
5月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【7月更文挑战第28天】在Android开发中,掌握底层机制至关重要。从Dalvik到ART, Android通过采用AOT编译在应用安装时预编译字节码至机器码,显著提升了执行效率。ART还优化了垃圾回收,减少内存占用及停顿。为了优化性能,可减少DEX文件数量、优化代码结构利用内联等技术、合理管理内存避免泄漏,并使用ART提供的调试工具。
125 7
|
2月前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
3月前
|
存储 Java 编译器
🔍深入Android底层,揭秘JVM与ART的奥秘,性能优化新视角!🔬
【9月更文挑战第12天】在Android开发领域,深入了解其底层机制对提升应用性能至关重要。本文详述了从早期Dalvik虚拟机到现今Android Runtime(ART)的演变过程,揭示了ART通过预编译技术实现更快启动速度和更高执行效率的奥秘。文中还介绍了ART的编译器与运行时环境,并提出了减少DEX文件数量、优化代码结构及合理管理内存等多种性能优化策略。通过掌握这些知识,开发者可以从全新的角度提升应用性能。
75 11
|
5月前
|
存储 监控 Java
揭秘Java虚拟机:探索JVM的工作原理与性能优化
本文深入探讨了Java虚拟机(JVM)的核心机制,从类加载到垃圾回收,再到即时编译技术,揭示了这些复杂过程如何共同作用于Java程序的性能表现。通过分析现代JVM的内存管理策略和性能监控工具,文章提供了实用的调优建议,帮助开发者有效提升Java应用的性能。
81 3
|
5月前
|
监控 算法 Java
深入理解Java虚拟机:内存管理与性能优化
在Java的世界里,虚拟机(JVM)是幕后的守护者,它默默地支撑着每一个字节码的运行。本文将揭开JVM的神秘面纱,探讨其内存管理机制及如何通过调优提升应用性能。从堆内存的分配到垃圾回收的策略,再到实践中的性能监控与调优技巧,我们将一同走进JVM的内部世界,学习如何让我们的Java程序跑得更快、更稳。
|
Arthas 监控 算法
JVM调优篇:探索Java性能优化的必备种子面试题
本文将带你深入了解JVM调优的重要性、常见问题以及一些实用的调优工具和方法,助你在面试的过程中轻松应对
193 0
JVM调优篇:探索Java性能优化的必备种子面试题
|
7月前
|
存储 算法 Java
深入解析Java虚拟机(JVM):技术原理与性能优化
深入解析Java虚拟机(JVM):技术原理与性能优化
95 1