目录
2.4 程序计数器(Program Counter Register)
2.5 本地方法栈(Native Method Stack)
3.3 垃圾回收器(Garbage Collector, GC)
4. 本地方法接口(JNI, Java Native Interface)
5.1.1. 标记-清除算法(Mark-Sweep Algorithm)
5.1.2. 复制算法(Copying Algorithm)
5.1.3. 标记-整理算法(Mark-Compact Algorithm)
5.1.4. 分代收集算法(Generational Collection)
5.1.5. 增量式垃圾回收(Incremental GC)
5.1.6. 并发标记清除算法(Concurrent Mark-Sweep, CMS)
5.1.7. G1垃圾回收器(Garbage First, G1 GC)
1. 类加载器(Class Loader)
类加载器是JVM中负责动态加载类的组件。它的工作流程包含以下几个关键步骤:
1.1 加载(Loading)
-
功能:将类的字节码读取到内存中。此步骤由类加载器的
findClass
方法实现。 -
过程:
-
查找:JVM根据类的全限定名(如
java.lang.String
)在类路径下查找相应的.class
文件。 -
读取:找到后,将文件内容读取为字节数组。
-
1.2 连接(Linking)
连接阶段包含验证、准备和解析三个子步骤:
-
验证:
-
目的:确保加载的字节码是安全的、合法的,防止恶意代码的执行。
-
过程:检查字节码格式、符号引用的有效性、类的继承关系等。
-
工具:使用类验证器(Class Verifier)进行验证。
-
-
准备:
-
目的:为类变量分配内存并设置初始值。
-
过程:为静态变量分配内存空间,并赋予默认值(如
0
、null
等)。 -
注意:此时并不执行类的初始化代码。
-
-
解析:
-
目的:将符号引用转换为直接引用,符号引用是一种字符串形式的表示。
-
过程:将类中的方法、字段等符号引用转换为内存地址,以便后续快速访问。
-
1.3 初始化(Initialization)
-
功能:为类的静态变量赋值,并执行静态代码块。
-
过程:
-
JVM首先对静态变量进行初始化,并将其赋予在类中定义的初始值。
-
然后,执行静态初始化块,这个块在类加载时只会执行一次。
-
1.4 类加载器的层次结构
JVM中的类加载器是分层次的,主要包括以下几种:
-
引导类加载器(Bootstrap ClassLoader):
-
功能:加载Java核心库(如
rt.jar
中的类),实现JVM的基础功能。 -
实现:用C++编写,是JVM的根加载器,无法被直接访问。
-
-
扩展类加载器(Extension ClassLoader):
-
功能:加载
jre/lib/ext
目录中的类库或其他扩展库。 -
实现:由Java实现,并继承自
ClassLoader
类。
-
-
应用类加载器(Application ClassLoader):
-
功能:加载用户应用程序的类和库,通常是classpath下的类。
-
实现:也是Java实现,用户最常用的类加载器。
-
-
自定义类加载器:
-
用户可以通过继承
ClassLoader
类实现自己的类加载器,以实现特定的类加载策略,例如热加载、按需加载等。
-
2. 运行时数据区(Runtime Data Areas)
JVM在运行Java程序时会将内存划分为几个主要区域,每个区域有不同的用途和功能:
2.1 堆(Heap)
-
功能:存储所有的对象和数组,是JVM内存管理的主要区域,堆内存是GC(垃圾回收器)管理的区域。
-
结构:
-
年轻代(Young Generation):新创建的对象通常在年轻代分配内存。年轻代通常被划分为三个区域:
-
Eden区:新对象的默认分配区域。
-
Survivor S0区和S1区:用来存放经历过一次垃圾回收的存活对象。
-
-
老年代(Old Generation):存储长时间存活的对象。当对象在年轻代中经历多次垃圾回收后,仍然存活就会被移动到老年代。
-
2.2 方法区(Method Area)
-
功能:存储类的信息,包括类结构、运行时常量池、字段、方法数据等。
-
元空间(Metaspace):
-
从Java 8开始,方法区的实现方式变为Metaspace,它使用本地内存(Native Memory),不再使用堆。元空间的大小仅受系统内存限制。
-
功能:存放类的元数据,类的结构、常量、静态变量等信息。
-
2.3 栈(Stack)
-
功能:每个线程都有自己的栈,用于存储局部变量、操作数、方法返回值等信息。
-
栈帧(Stack Frame):
-
每个方法调用会创建一个新的栈帧,栈帧中包含:
-
局部变量表:存储方法的参数和局部变量。
-
操作数栈:用于存放操作数和计算结果。
-
方法返回地址:指向方法返回时的地址。
-
-
2.4 程序计数器(Program Counter Register)
-
功能:每个线程都有一个独立的程序计数器,用于记录当前线程所执行的字节码的地址。
-
特点:
-
线程私有:每个线程有自己的计数器,线程间不会相互影响。
-
当线程执行方法时,程序计数器记录该方法的字节码地址。
-
2.5 本地方法栈(Native Method Stack)
-
功能:与Java栈类似,但专门用于处理Java中调用的本地方法(Native Method)。
-
内容:为本地方法的参数、局部变量等分配内存。与Java栈一样,方法调用时也会创建栈帧。
3. 执行引擎(Execution Engine)
执行引擎是JVM的核心部分,负责执行字节码。
3.1 解释器(Interpreter)
-
功能:逐行读取字节码,并将其转换为机器指令执行。
-
优缺点:
-
优点:适合开发和调试,能够快速执行代码,不需要编译步骤。
-
缺点:执行速度较慢,因为每次都需要解析字节码。
-
3.2 即时编译器(JIT Compiler)
-
功能:监测到频繁调用的方法,并将其编译成本地机器代码,以提高执行效率。
-
工作机制:
-
在方法被调用时进行编译,并将编译后的代码存储在内存中,后续调用直接使用已编译的代码。
-
-
优化技术:
-
代码优化:JIT编译器会进行各种优化,例如内联、循环优化、死代码消除等。
-
热点代码:通过热图(HotSpot)机制监测并编译频繁调用的方法。
-
3.3 垃圾回收器(Garbage Collector, GC)
-
功能:负责自动回收不再使用的对象的内存,防止内存泄漏。
-
类型:
-
Serial GC:使用单线程进行垃圾回收,适用于小型应用。
-
Parallel GC:多线程并行处理,适合多核CPU,能够提高回收速度。
-
Concurrent Mark-Sweep (CMS):并发标记清除,适合低延迟应用,能够在应用程序运行时进行垃圾回收。
-
Garbage First (G1):采用分区管理的方式,适合大堆内存的应用,能够提供更好的回收效率。
-
4. 本地方法接口(JNI, Java Native Interface)
JNI是Java提供的接口,使得Java代码能够调用其他语言(如C/C++)编写的本地方法。
4.1 使用场景
-
性能优化:某些性能要求高的场景,可以使用本地方法实现更高效的算法。
-
现有库:利用已有的C/C++库,避免重复开发。
-
系统级操作:需要调用底层系统功能时,可以通过JNI实现。
4.2 JNI的工作原理
-
声明本地方法:在Java代码中声明本地方法,使用
native
关键字。 -
实现本地方法:在C/C++中实现本地方法,使用JNI提供的API与Java对象进行交互。
-
加载本地库:使用
System.loadLibrary()
方法加载本地库,以便在Java中调用本地方法。
4.3 JNI的优缺点
-
优点:能够调用高性能的底层代码,利用现有的C/C++库。
-
缺点:增加了开发复杂性,跨语言调用可能
5. 垃圾回收(Garbage Collection)
垃圾回收是JVM内存管理的重要机制,负责自动回收不再使用的对象的内存。
5.1 垃圾回收算法
5.1.1. 标记-清除算法(Mark-Sweep Algorithm)
原理
标记-清除算法是最基本的垃圾回收算法之一。它分为两个阶段:
-
标记阶段:从GC Roots(根对象)开始,遍历所有可达对象,并将其标记为“存活”。
-
清除阶段:遍历整个堆,将未被标记的对象视为垃圾,并清除这些对象所占用的内存。
优点
-
实现简单:算法的概念和实现相对简单,容易理解和实现。
-
无需额外空间:它直接在堆上操作,不需要额外的内存空间来存储临时数据。
缺点
-
产生内存碎片:由于内存清理是直接释放未使用的空间,长期运行可能会导致内存碎片,分配新对象时可能遇到“碎片化”问题,影响性能。
-
GC停顿时间长:标记和清除两个阶段都会暂停应用程序,容易导致长时间的停顿(Stop-the-World)。
5.1.2. 复制算法(Copying Algorithm)
原理
复制算法将堆空间分成两部分,每次只使用其中一部分。垃圾回收时,将所有存活的对象从当前正在使用的空间复制到另一块未使用的空间,清空旧空间。
-
常用于年轻代的GC(如新生代GC),因为新生代对象存活率较低,复制的对象较少。
优点
-
内存分配快:由于每次都在连续的内存空间上分配对象,不需要考虑碎片问题,分配效率非常高。
-
清理效率高:只需要处理存活对象的复制,内存可以一次性全部清空,不用像标记-清除算法那样逐一清理无用对象。
缺点
-
空间浪费:复制算法需要预留两块内存空间,未使用的内存部分一直处于空闲状态,空间利用率不高。
-
适合小堆:对大堆使用该算法效率不高,因为需要较多的内存空间来进行复制操作。
5.1.3. 标记-整理算法(Mark-Compact Algorithm)
原理
标记-整理算法是对标记-清除算法的改进,标记阶段和标记-清除算法相同,但在清除阶段不是直接清除不可达对象,而是将存活对象压缩到堆的一个端,保持存活对象的连续性,然后清理剩余的内存空间。
优点
-
解决内存碎片问题:通过压缩存活对象,避免了内存碎片的产生。
-
适合老年代:由于老年代对象存活率高,压缩操作相对有意义,能够提高内存利用率。
缺点
-
GC停顿时间长:对象的整理和压缩需要移动大量对象,特别是当存活对象较多时,垃圾回收的停顿时间会较长。
-
性能开销较大:对象移动涉及复制数据,导致GC过程中开销增大。
5.1.4. 分代收集算法(Generational Collection)
原理
分代收集算法基于对象生命周期的特点,将堆划分为年轻代和老年代。年轻代中的对象存活时间短,老年代中的对象存活时间长。不同代使用不同的GC算法:
-
年轻代使用复制算法(如Minor GC),因为对象存活率低,回收速度快。
-
老年代使用标记-清除或标记-整理算法(如Major GC),因为老年代对象存活率高。
优点
-
优化性能:通过分代收集,减少老年代的GC频率,提高垃圾回收效率。
-
减少GC停顿时间:年轻代GC回收较频繁,但由于年轻代空间较小,回收时间短,老年代GC相对较少发生,减少了全局GC的停顿时间。
缺点
-
GC复杂性提高:需要分别为年轻代和老年代设计不同的GC策略,增加了垃圾回收的复杂性。
-
老年代GC停顿时间较长:当老年代GC发生时(Full GC),会导致较长的应用程序停顿,特别是老年代空间较大时。
5.1.5. 增量式垃圾回收(Incremental GC)
原理
增量式垃圾回收通过分阶段执行GC,避免一次性暂停整个应用程序。它将GC的工作拆分为若干小块,每次只回收一部分对象,尽量减少GC对应用程序的干扰。
优点
-
降低GC停顿时间:通过分阶段执行,减少每次GC造成的应用停顿时间,适用于对延迟敏感的应用。
-
提高并发性:可以与应用程序交替执行,减少停顿影响。
缺点
-
降低回收效率:由于每次只回收一小部分对象,整体垃圾回收的效率较低。
-
复杂性高:需要更复杂的GC逻辑来维护并发回收的状态。
5.1.6. 并发标记清除算法(Concurrent Mark-Sweep, CMS)
原理
CMS算法是为了减少老年代GC的停顿时间而设计的一种并发垃圾回收算法。CMS垃圾回收的步骤包括:
-
初始标记:标记GC Roots直接可达的对象。
-
并发标记:并发地标记所有从GC Roots可达的对象。
-
重新标记:在并发标记的基础上,重新扫描标记新产生的可达对象。
-
并发清除:并发地清除不可达的对象。
优点
-
低停顿:CMS的并发标记和并发清除阶段不会暂停应用程序,适用于对低延迟要求较高的应用。
-
提高吞吐量:减少长时间的GC停顿,提高应用的整体性能。
缺点
-
浮动垃圾:由于CMS在并发清除时,程序可能会继续分配新对象,这些对象在清除阶段无法被识别,导致下一次GC需要回收这些“浮动垃圾”。
-
内存碎片:CMS不会对内存进行整理,容易产生碎片,可能导致频繁的Full GC。
-
较高的CPU开销:并发标记和清除阶段会占用CPU资源,影响程序性能。
5.1.7. G1垃圾回收器(Garbage First, G1 GC)
原理
G1 GC是为解决CMS的缺点而设计的现代垃圾回收器,适用于大堆内存的场景。G1 GC将堆划分为多个独立的区域(Region),回收时优先选择垃圾最多的区域进行回收(Garbage First)。G1 GC还支持将GC停顿时间控制在指定的范围内。
优点
-
避免内存碎片:G1 GC在回收时会压缩存活对象,减少内存碎片问题。
-
可预测的GC停顿:可以通过参数(如
-XX:MaxGCPauseMillis
)控制GC的最大停顿时间,适合低延迟需求的应用。 -
适用于大内存:G1 GC对大堆内存的应用程序优化较好,能够高效地回收内存。
缺点
-
复杂性较高:G1 GC内部实现复杂,调优难度较大。
-
占用更多CPU资源:由于G1涉及到内存整理和压缩,相比其他垃圾回收器,CPU开销较高。
5.2 垃圾回收的触发机制
5.2.1 Minor GC
触发时机:
-
当年轻代的Eden区满时。
-
新对象分配导致Survivor区也满。
优点:
-
通常较快,因为只涉及年轻代。
-
可以迅速回收短命对象,优化内存使用。
缺点:
-
如果频繁触发,可能会影响应用性能。
-
在极端情况下,可能会导致多次GC操作。
5.2.2. Major GC
触发时机:
-
主要在老年代内存不足时。
-
若Survivor区对象晋升到老年代导致空间紧张。
优点:
-
回收老年代中的长命对象,有助于释放较大内存。
缺点:
-
比Minor GC耗时更长,可能导致应用明显停顿。
-
若触发频繁,可能影响整体应用性能。
5.2.3. Full GC
触发时机:
-
手动调用
System.gc()
。 -
老年代内存不足或某些内存分配失败。
-
特定条件下,如JVM内存策略调整。
优点:
-
回收整个堆内存,清理存活对象,提高内存利用率。
-
适合长时间运行的应用,防止内存泄漏。
缺点:
-
停顿时间最长,可能对用户体验造成负面影响。
-
触发频繁可能导致系统性能下降,增加响应时间。
6.jvm调优
6.1. 内存调优
6.1.1 堆内存调优
-
堆的初始大小和最大大小
-
-Xms<size>
:设置JVM启动时堆内存的初始大小。 -
-Xmx<size>
:设置堆内存的最大大小。合理的设置可以避免频繁的GC。
-
-
年轻代和老年代比例
-
-XX:NewRatio=<ratio>
:设置年轻代和老年代的比例。默认值为2,即年轻代占总堆的1/3,老年代占2/3。根据应用的对象存活时间调整这个比例。 -
-XX:NewSize=<size>
和-XX:MaxNewSize=<size>
:设置年轻代的初始和最大大小。
-
6.1.2 方法区调优
-
Metaspace调优
(Java 8及之后):
-
-XX:MetaspaceSize=<size>
:设置Metaspace的初始大小。 -
-XX:MaxMetaspaceSize=<size>
:限制Metaspace的最大大小。合理设置可以防止过度使用本地内存。
-
6.2. 垃圾回收调优
6.2.1 垃圾回收器选择
-
选择合适的垃圾回收器
-
G1 GC:适合大内存、低延迟需求的应用,使用
-XX:+UseG1GC
启用。 -
Parallel GC:适合CPU密集型的应用,使用
-XX:+UseParallelGC
启用。 -
CMS GC:适合需要低延迟的应用,使用
-XX:+UseConcMarkSweepGC
启用。
-
6.2.2 垃圾回收日志
-
启用GC日志记录,以监控GC的行为:
-
-Xloggc:<file>
:将GC日志输出到指定文件。 -
-XX:+PrintGCDetails
和-XX:+PrintGCDateStamps
:详细记录GC的细节和时间戳。
-
6.2.3 调整GC频率和时间
-
GC相关参数
-
-XX:MaxGCPauseMillis=<time>
:设置G1 GC的最大暂停时间。 -
-XX:G1HeapRegionSize=<size>
:设置G1的堆区域大小,合理的区域大小有助于提升GC效率。
-
6.3. 性能优化
6.3.1 线程和并发调优
-
设置线程栈大小
-
-Xss<size>
:设置每个线程的栈大小,过小可能导致StackOverflowError,过大则浪费内存。
-
6.3.2 JIT编译调优
-
JIT编译器选项
-
-XX:CompileThreshold=<count>
:设置方法调用的计数阈值,超过这个次数后才进行JIT编译。适用于需要优化的特定方法。
-
6.4. 监控与分析工具
6.4.1 使用JVisualVM
-
监控JVM的内存使用、线程、CPU等性能指标。通过其插件功能,能够分析内存泄漏、性能瓶颈等问题。
6.4.2 JConsole
-
图形化工具,用于实时监控JVM的内存、线程、类加载等信息。
6.4.3 GC分析
-
使用工具如MAT(Memory Analyzer Tool)分析GC日志和内存堆转储,识别内存泄漏和性能问题。
6.5. 实际调优案例
-
高并发场景:选择G1 GC,调整年轻代和老年代的比例,确保对象能快速回收。
-
低延迟需求:使用CMS GC,优化最大暂停时间,确保应用响应迅速。
-
内存密集型应用:增加堆内存和Metaspace大小,避免频繁的GC。