JVM性能优化专题

2023-05-15 17:53:51 浏览数 (2)

JVM加载Class文件的原理机制

1. 编译阶段,Java源文件被编译成.class文件,包含字节码和元数据。

2. 类加载阶段,.class文件由类加载器加载进JVM。类加载器通常采用双亲委派模型,首先请求父类加载器加载,如果父类加载器无法加载则自己加载。

3. 链接阶段,将类的二进制数据合并到JVM运行状态之中的过程。包含验证、准备和解析三个步骤。

- 验证:确保加载的类满足JVM规范,没有安全方面的问题。

- 准备:为类的静态变量分配内存,并设置默认初始化值。

- 解析:将类中的符号引用替换为直接引用。

4. 初始化阶段,如果该类具有超类,则对其超类进行初始化。然后执行<clinit>()方法,完成类的静态变量初始化。

5. 使用阶段,实例化对象和调用方法等,Class对象在JVM中一直有效,直到JVM退出。

6. 卸载阶段,不会主动卸载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. 垃圾收集算法:

- 标记清除:标记出不可达对象,然后统一回收。会产生大量内存碎片。

- 标记压缩:标记不可达对象,然后移动可达对象,最后清理掉边界以外的内存空间。可以减少内存碎片。

- 分代收集:将内存分为新生代和老年代,新生代采用复制算法,老年代采用标记清除/压缩算法。新生代对象容易回收,老年代对象存活时间长。

4. 垃圾收集器:

- Serial:单线程收集器,STW(Stop The World)时间长,但简单高效。适用于小内存和单CPU环境。

- Parallel:多线程收集器,STW时间短,CPU密集型,适用于中等并发的服务器环境。

- CMS:并发收集器,心臟不停跳。并发收集消度高但碎片多,适用于快速响应的环境。

- G1:分代收集器,并行和并发相结合,整理碎片和回收效率高,适用于大内存和并发环境。

5. 垃圾收集调优:根据应用的特点选择合适的垃圾收集器和相应的参数配置,达到一个短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. 标记清除:简单,运行速度快,但会产生大量内存碎片。适用于新生代。

代码语言:javascript复制

public void gc() {
    mark(); // 标记所有可达对象
    sweep(); // 清除所有未标记对象
}

2. 标记压缩:需要付出额外代价移动内存,但可以减少内存碎片。适用于老年代。

代码语言:javascript复制

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释放内存。

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

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

0 人点赞