作者:
逍遥Sean
简介:一个主修Java的Web网站\游戏服务器后端开发者
主页:https://blog.csdn.net/Ureliable
觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言!
前言
很多Java开发岗位要求实际调优经验,难倒了JVM小白
想要掌握JVM调优首先理解JVM的组成和机制,明白GC的原理和算法,掌握调优工具,知道参数配置,随后在开发过程中根据项目需要进行配置或是机制优化
另外,博主之前分享了一篇关于JVM的面试经验,欢迎友友学习评价~
文章链接: Java开发第二轮面试被疯狂问JVM相关,被整懵了!!赶紧好好复习一下
Java虚拟机(JVM)内存分配主要分为以下几个步骤:
堆内存分配:Java中的对象都存储在堆内存中,当程序需要创建一个新对象时,JVM就会在堆中分配一块内存空间
,并返回该对象的引用。
栈内存分配:栈内存用于存放方法调用中的数据,如方法的参数、局部变量等
。每当调用一个新方法时,JVM就会为该方法分配一块栈内存空间,并将该方法的参数和局部变量存储在该内存空间中。
方法区内存分配:方法区用于存储类的元数据、静态变量、常量等数据,通常也称为“永久代”
。当JVM需要加载一个类时,它就会在方法区中分配一块内存空间,并将该类的元数据等信息存储在该内存空间中。
本地内存分配:本地内存用于存储使用JNI(Java Native Interface)调用的本地方法库的数据
。当程序需要调用本地方法库时,JVM就会为该方法库分配一块本地内存空间,并将相关数据存储在该内存空间中。
类装载器(Class Loader):Java代码 -----> 字节码 的编译过程
。JVM中的类装载器用于加载Java程序的类文件。它会将Java程序中的类文件加载到内存中,并生成对应的Java类。
运行时数据区(Runtime Data Area):把上一步编译得到的字节码加载到内存中
。JVM中的运行时数据区用于存储Java程序中的数据。它包括方法区、堆、栈、程序计数器和本地方法栈。
执行引擎(Execution Engine):解析上一步加载而来的字节码,翻译成为系统指令,交由CPU执行
。JVM中的执行引擎用于执行由字节码文件生成的指令。它是将Java程序转换为机器指令的重要组成部分。
本地库接口(Java Native Interface(JNI)):JVM中的JNI允许Java程序调用本地应用程序。JNI提供了一种标准的接口,使Java程序可以与C或C++程序进行交互。
Java虚拟机语言规范(Java Virtual Machine Specification):诸如IO之类的由其他语言写成的本地库接口
。JVM中的规范文件定义了Java编程语言的语法和语义。它描述了JVM如何解释Java程序,并将其转换为机器指令。
调试接口(Debugging Interface):JVM中的调试接口允许开发人员在代码执行期间调试Java程序。它提供了一些开发工具,如JMX(Java Management Extensions)和JVMPI(Java Virtual Machine Profiler Interface)。
程序计数器:行号指示器,通过改变该值,以选取下一步的指令
Java虚拟机栈:局部变量、方法出口等,为JVM服务
本地方法栈:局部变量、方法出口等,为本地Native方法服务
堆区:内存最大的一块,所有的对象实例都在这里分配内存
方法区:常量、静态变量等
类加载:JVM需要先加载类定义。如果定义这个类的.class文件还没有被加载,JVM就会把这个文件读进来,然后对它进行解析和验证。
分配内存:当类被加载后,JVM需要在堆上分配内存来存储对象。根据对象的类型和大小,JVM会在堆上分配一段连续的内存空间。
初始化成员变量:在对象创建的过程中,JVM需要初始化对象的所有成员变量。如果成员变量是基本类型,JVM会给其默认值;如果成员变量是引用类型,JVM会初始化为null。
执行构造函数:当内存空间分配完成并且成员变量初始化完成后,JVM会调用构造函数对对象进行初始化。
返回对象引用:构造函数执行完成后,对象就被创建成功了。JVM会返回对象的引用,开发者就可以通过该引用来访问和操作该对象。
分代垃圾回收机制:不同的对象生命周期不同。把不同生命周期的对象放在不同代上,不同代上采用最合适它的垃圾回收方式进行回收。
JVM中共划分为三个代:年轻代、年老代和持久代,
年轻代:存放所有新生成的对象;
年老代:在年轻代中经历了N次垃圾回收仍然存活的对象,将被放到年老代中,故都是一些生命周期较长的对象;
持久代:用于存放静态文件,如Java类、方法等。
新生代的垃圾收集器命名为“minor gc
”,老生代的GC命名为”Full Gc 或者Major GC
”.其中用System.gc()强制执行的是Full Gc
.
判断对象是否需要回收的方法有两种:
- 引用计数。当某对象的引用数为0时,便可以进行垃圾收集。
- 对象引用遍历。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。
触发GC(Garbage Collector)的条件:
GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。
Java堆内存不足时,GC会被调用。
简要记忆:
标记-清除
:无用对象全部干掉
标记-整理
:有用对象都向一边移动,边界以外的全部干掉
复制算法
:左边内存快满时,将其中要保留的对象复制到右边内存中,然后整体干掉左边内存。右边同理,内存利用率仅有一半
分代算法
:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
完整概念:
- 标记-清除算法(Mark-Sweep):首先标记所有活动对象,然后清除所有未标记的对象。
- 复制算法(Copying):将存活对象复制到一块新的内存中,然后清除旧的内存。
- 标记-整理算法(Mark-Compact):首先标记所有活动对象,然后将所有存活对象移到一端,最后清除端边界之外的所有对象。
- 分代收集算法(Generational):根据对象的生命周期划分为不同代,根据代的特点采用不同的垃圾回收算法,如新生代采用复制算法,老年代采用标记-整理算法。
- 并发标记-清除算法(Concurrent Mark-Sweep):在应用程序运行的同时标记和清除垃圾,减少停顿时间。
- G1算法(Garbage-First):将堆内存分成多个大小相等的区域,根据垃圾多少先清理垃圾最多的区域,提高垃圾回收效率。
JVM 调优的常用工具包括以下几种:
这些工具都有各自的特点和优劣,具体使用取决于实际调优需求。
JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm这两款视图监控工具。
•jconsole
:用于对 JVM 中的内存、线程和类等进行监控;
•jvisualvm
: JDK自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、 gc 变化等。
常用版:
• -Xms2048M最小内存2048M
• -Xmx2048M最大内存2048M
• -XX:NewRatio=4设置年轻的和老年代的内存比例为 1:4
• -XX:SurvivorRatio=8设置新生代 Eden 和 Survivor 比例为 8:2
• -XX:+UseConcMarkSweepGC指定使用 CMS + Serial Old 垃圾回收器组合;
• -XX:+PrintGC开启打印 gc 信息;
• -XX:+PrintGCDetails打印 gc 详细信息。
完整版:
- -Xms:设置JVM初始堆大小
- -Xmx:设置JVM最大堆大小
- -Xmn:设置年轻代大小
- -XX:SurvivorRatio:设置Eden区和Survivor区的比例
- -XX:MaxPermSize:设置永久代大小
- -XX:MaxTenuringThreshold:设置对象晋升年龄的最大值
- -XX:NewRatio:设置老年代与年轻代的比例
- -XX:+UseConcMarkSweepGC:使用CMS垃圾回收器
- -XX:+UseParallelGC:使用并行垃圾回收器
- -XX:ParallelGCThreads:设置并行垃圾回收线程数
- -XX:+CMSParallelRemarkEnabled:启用CMS回收完成后并行分析标记
- -XX:+UseParallelOldGC:使用并行老年代垃圾回收器
- -XX:MaxGCPauseMillis:设置最大垃圾回收停顿时间
- -XX:+AggressiveOpts:启用侵略性优化特性
- -XX:+OptimizeStringConcat:启用字符串拼接优化
- -XX:+UseBiasedLocking:启用偏向锁
- -XX:CompileThreshold:设置JIT编译阈值
调整内存分配参数:包括最大堆内存、最小堆内存、新生代大小等,以避免频繁的垃圾回收和内存溢出。
优化垃圾回收:可以通过选择不同的垃圾回收器,调整回收器选项等来实现。
选择合适的JVM版本:新版本的JVM可能会提供更好的性能和稳定性。
减少对象的创建:避免频繁的对象创建和销毁,尽量重用已经创建的对象。
使用线程池:尽量减少线程的创建和销毁,使用线程池来管理线程。
避免过度同步:使用适当的同步控制方式,避免过度使用锁和同步方法。
优化代码结构和算法:使用更高效的算法和数据结构,避免不必要的计算和内存消耗。
监控JVM运行状态:及时发现问题并解决,包括内存泄漏、线程死锁等。
使用JVM调试工具:如JVisualVM、JConsole和Java Flight Recorder等,以便更好地分析和解决问题。
注:以上方案须具体问题具体分析,不同应用场景可能需要不同的调优方法。