前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM性能优化专题

JVM性能优化专题

原创
作者头像
疯狂的KK
发布2023-05-15 17:53:51
3380
发布2023-05-15 17:53:51
举报
文章被收录于专栏:Java项目实战Java项目实战

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的对象,从而彻底解决内存泄漏问题。良好的编码习惯可以极大的减少内存泄漏

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档