JVM对象逃逸

1, 是JVM优化技术,它不是直接优化手段,而是为其它优化手段提供依据。

逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。Java在Java SE 6u23以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析,同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。


2,逃逸分析主要就是分析对象的动态作用域。

基于逃逸分析,一个对象会可能会被用三种逃逸状态标记:

  • 全局级别逃逸:一个对象可能从一个方法或者当前线程中逃逸。再明确一点,如果一个对象被作为一个方法的返回值,那么对象被标记为全局逃逸状态。如果一个对象作为类静态字段(static field)或者类字段(field),同样会被标记为全局逃逸状态。另外,如果我们复写了一个方法的finalize()方法,那么这个类的对象都会被标记为全局逃逸状态并且一定会放在堆内存中,这也符合情理,因为这些对象需要对于JVM的finalizer必须是可见的(所以发生逃逸了)。当然,还有其他的一些情况也会让对象标记为全局逃逸状态。
  • 参数级别逃逸:如果一个对象被作为参数传递给一个方法,但是在这个方法之外无法访问或者对其他线程不可见,这个对象标记为参数级别逃逸。
  • 无逃逸状态:一个对象不会产生逃逸

标记为全局级别逃逸或者参数级别逃逸的对象必须在堆中分配空间,但是参数级别逃逸是可能在内存中去掉对象同步锁的,因为上面已经解释,参数级别逃逸对象不会被其他线程访问。

无逃逸状态的对象的内存分配会更加自由,可能会在栈上分配,也可能会在堆上分配。事实上,在某些情况下,甚至根本不会去创建一个对象,而直接使用该对象的标量值代替,比如仅仅在栈上创建一个int,去代替一个Integer对象。因为只有一个线程可以访问该对象,所以对象上的同步锁自然会被去掉。例如,我们使用无逃逸状态的StringBuffer(较之StringBuilder,StringBuffer是线程安全的,所有方法都是synchronized),那么这种情况下,所有方法的同步锁都会被去掉,提高执行效率。


3,逃逸有两种:方法逃逸和线程逃逸。
        方法逃逸(对象逃出当前方法):
                当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其它方法中。

        线程逃逸((对象逃出当前线程):
                这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量

4,如果不存在逃逸,则可以对这个变量进行优化
     (1)栈上分配。
                在一般应用中,不会逃逸的局部对象占比很大,如果使用栈上分配,那大量对象会随着方法结束而自动销毁,垃圾回收系统压力就小很多。
     (2)同步消除
                线程同步本身比较耗时,如果确定一个变量不会逃逸出线程,无法被其它线程访问到,那这个变量的读写就不会存在竞争,对这个变量的同步措施可以清除。
     (3)标量替换。
                1)标量就是不可分割的量,java中基本数据类型,reference类型都是标量。相对的一个数据可以继续分解,它就是聚合量(aggregate)。
                2)如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换。
                3)如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候将可能不创建这个对象,而改为直接在栈上创建若干个成员变量。

5,逃逸分析还不成熟。
    (1)不能保证逃逸分析的性能收益必定高于它的消耗。
                判断一个对象是否逃逸耗时长,如果分析完发现没有几个不逃逸的对象,那时间就白白浪费了。
    (2)基于逃逸分析的优化手段不成熟,如上面提到的栈上分配,由于hotspot目前的实现方式导致栈上分配实现起来复杂。

6,相关JVM参数
        -XX:+DoEscapeAnalysis 开启逃逸分析
        -XX:+PrintEscapeAnalysis 开启逃逸分析后,可通过此参数查看分析结果。
        -XX:+EliminateAllocations 开启标量替换
        -XX:+EliminateLocks 开启同步消除
        -XX:+PrintEliminateAllocations 开启标量替换后,查看标量替换情况。

 

TLAB
JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。


 

### JVM 逃逸分析概述 JVM中的逃逸分析是一种编译期优化技术,用于判断对象是否会逃逸出当前线程或方法的作用域。通过这种分析,JDK能够做出一些优化决策来提高性能,比如栈上分配、同步消除等[^4]。 ### 逃逸分析引发内存泄漏的原因 当对象未正确处理其生命周期时,在某些情况下即使启用了逃逸分析也可能间接造成内存泄漏: - **错误的对象共享**:如果程序逻辑设计不当,使得原本应该局部化的对象意外地被其他部分持有引用,则这些对象无法及时回收,从而形成潜在的内存泄露风险。 - **误判为不逃逸的情况**:对于复杂的数据结构操作,尤其是涉及多线程环境下的并发访问模式下,可能存在难以精确判定是否发生逃逸的情形。此时可能会导致不必要的堆内存在长时间得不到释放而累积成内存泄漏现象。 ### 解决方案 针对上述由逃逸分析可能带来的问题,可以从以下几个方面着手解决: #### 合理调整GC策略与参数配置 适当调节垃圾收集器的选择及其相关参数可以帮助更好地管理应用程序内的资源消耗情况。例如启用G1 GC并合理设定初始和最大堆大小(-Xms,-Xmx),这有助于维持稳定的运行状态,防止由于频繁Full GC造成的性能瓶颈以及由此产生的隐含内存泄漏隐患[^1]。 ```bash java -XX:+UseG1GC -Xms512m -Xmx4g MyApplication ``` #### 审查代码逻辑确保无异常引用保留 仔细审查源码中涉及到对象创建及传递的部分,特别是那些跨作用范围使用的实例变量或者静态成员字段。确认它们不会无意间延长目标对象的生命周朗,进而阻碍正常的垃圾回收过程。 #### 利用工具辅助诊断排查 借助专业的性能剖析工具如VisualVM, MAT(Memory Analyzer Tool)等对正在执行的应用进程实施监控跟踪,定位具体引起内存占用过高的区域,并据此采取针对性措施加以改进。 #### 正确理解并运用逃逸分析特性 虽然现代版本Java已经默认开启此功能,但在特定场景下调优仍有必要深入了解该机制的工作原理。必要时可通过显式指定`-XX:-DoEscapeAnalysis`关闭它来进行对比测试,观察实际效果变化后再做决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值