JVM内存增强分析

1 JVM 逃逸分析

1.1 背景

       随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会
导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
Java 虚拟机中,对象是在 Java 堆中分配内存的,这是一个普遍的常识。但是,有
一种特殊情况,那就是如果经过逃逸分析( Escape Analysis )后发现,一个对象并没有逃
逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃
圾回收了。这也是最常见的堆外存储技术。
逃逸分析技术到现在还不是很成熟,虽然经过逃逸分析可以做标量替换、栈上分配、锁
消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过
程。

1.2 何为逃逸分析

       逃逸分析一种数据分析算法,基于此算法可以有效减少 Java 对象在堆内存中的分配。
Hotspot 虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象
分配到堆上。例如:
 >当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
 > 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。

1.3 逃逸分析案例演示

1.3.1 逃逸对象
    如下代码中的 StringBuffer 发生了逃逸,不会在栈上分配。

 

1.3.2 未逃逸对象
     当一个对象在方法内创建,又没有被外界引用,此对象为非逃逸对象。例如

 

1.3.3 逃逸分析参数设置
       在 JDK 1.7 版本之后, HotSpot 中默认就已经开启了逃逸分析,如果使用的是较早的
版本,开发人员则可以通过:
> 选项“ -XX:+DoEscapeAnalysis" 显式开启逃逸分析。
通过选项“ -XX:+PrintEscapeAnalysis" 查看逃逸分析的筛选结果。
建议:开发中能在方法内部应用对象的,就尽量控制在内部。

1.4 代码优化实践

1.4.1 概述

使用逃逸分析,编译器可以对代码做如下优化:

 ▪ 栈上分配:将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引

用不会发生逃逸,对象可能是栈上分配的候选。

▪ 同步省略:又称之为同步锁消除,如果一个对象被发现只有一个线程被访问到,那么对

于这个对象的操作可以不考虑同步。

▪ 分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问

到,那么对象的部分(或全部)可以不存储在内存,而是存储在 CPU 寄存器中。

1.4.2 栈上分配

 

 

        对如上代码运行测试时,分别开启和关闭逃逸分析,检查控制台日志的输出以及花费时
间上的不同。 
1.4.3 同步锁消除
        我们知道线程同步是靠牺牲性能来保证数据的正确性,这个过程的代价会非常高。程序
的并发行和性能都会降低。 JVM JIT 编译器可以借助逃逸分析来判断同步块所使用的锁
对象是否只能够被一个线程应用?假如是,那么 JIT 编译器在编译这个同步块的时候就会
取消对这部分代码上加的锁。这个取消同步的过程就叫同步省略,也叫锁消除。
1.4.4 标量替换分析
       所谓的标量( scalar )一般指的是一个无法再分解成更小数据的数据。例如, Java
的原始数据类型就是标量。相对的,那些还可以分解的数据叫做聚合量( Aggregate ), Java
中的对象就是聚合量,因为他可以分解成其他聚合量和标量。在 JIT 阶段,如果经过逃逸分
析,发现一个对象不会被外界访问的话,那么经过 JIT 优化,就会把这个对象分解成若干个
变量来代替。这个过程就是标量替换。例如:

 

 

   对于上面代码,假如开启了标量替换,那么 alloc 方法的内容就会变为如下形式:

 

       alloc 方法内部的 Point 对象是一个聚合量,这个聚合量经过逃逸分析后,发现他并
没有逃逸,就被替换成两个标量了。那么标量替换有什么好处呢?就是可以大大减少堆内存
的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。标量替换为栈上分配
提供了很好的基础。

2 内存溢出分析

 2.1 何为内存溢出

      内存中剩余的内存不足以分配给新的内存请求就会内存溢出。内存溢出可能直接导致系统溃。

 2.2 内存溢出的原因 

    导致内存溢出的原因,可能有:
▪ 创建的对象太大导致堆内存溢出
▪ 创建的对象太多导致堆内存溢出
▪ 方法出现了无限递归调用导致栈内存溢出
▪ 方法区内存空间不足导致内存溢出。
▪ 出现大量的内存泄漏

3 JVM 内存泄漏

3.1 内存泄漏分析

3.1.1 何为内存泄漏
       动态分配的内存空间,在使用完毕后未得到释放,结果导致一直占据该内存单元,直到程序
结束。这个现象称之为内存泄漏。因此良好的代码规范,可以有效地避免这些错误。
3.1.2 内存泄漏带来的问题
长时间运行,程序会变卡,性能严重下降。 ( 早期的只能手机这个问题很严重 )
  OutOfMemoryError 错误,系统直接挂掉。 猎才
3.1.3 导致内存泄漏的原因
▪ 大量使用静态变量( 静态变量与程序生命周期一样 )
▪ IO/连接资源用完没关闭 ( 记得执行 close 操作 )
▪ 内部类的使用方式存在问题( 实力内部类或默认引用外部类对象 )
▪ 缓存(Cache) 应用不当 ( 尽量不要使用强引用 )
ThreadLocal 应用不当 ( 用完记得执行 remove 操作 )
3.1.4 内存泄漏分析常用手段
▪ 应用内存分析工具 JProfiler, Java VisualVM 等。
▪ 在开发阶段时或者在测试环节,增加压力测试( 可考虑使用 JMeter 进行压力测试 )
▪ 认真对待开发工具给出的告警提示,该关闭的资源尽早关闭。
▪ 选择合适的时机进行代码 review
    通俗地说,我们可以将内存泄漏视为一种疾病,如果不治愈,随着时间的推移,它可能导致
致命的应用程序崩溃。内存泄漏很难解决,发现它们需要对 Java 语言的复杂掌握和掌握。
在处理内存泄漏时,没有一种万能的解决方案,因为泄漏可能通过各种不同的事件发生。
但是,如果我们采用最佳实践并定期执行严格的代码排查和分析,那么我们可以将应用程序
中内存泄漏的风险降到最低。

4 Java 四大引用分析

4.1 背景分析

       Java 中为了更好控制对象的生命周期,提高对象对内存的敏感度,设计了四种类型的引用。
按其在内存中的生命力强弱,可分为强引用、软引用、弱引用、虚引用。其中, " 强引用 "
用的对象生命力最强,其它引用引用的对象生命力依次递减。 JVM GC 系统被触发时,会
因对象引用的不同,执行不同的对象回收逻辑。

4.2 准备工作

创建一个类,作为演示应用,例如:

 4.3 引用类型应用

4.3.1强引用:
       通过对象自身类型或父类类型直接引用当前对象称之为强引用,例如:
        其中,使用 " 强引用 " 引用的对象,即便 JVM 内存空间不足,触发了 GC 操作, JVM 宁愿抛出
OutOfMemoryError 运行时错误,也不会回收有强引用引用的对象。如果对象已不可以通过 何" 强引用 " 所访问,就可以直接被垃圾回收了,但具体回收时机还是要看垃圾收集策略。
4.3.2 软引用:
       软引用通过 SoftReference 类型对象实现。 " 软引用 " 引用的对象,在 JVM 认为内存不足
时,会去试图回收其引用的对象。即 JVM 会确保在抛出 OutOfMemoryError 之前,清理
软引用指向的对象。软引用可以和一个引用队列 (ReferenceQueue) 联合使用,如果“软引
用”所引用的对象被垃圾回收器回收, Java 虚拟机就会把这个软引用加入到与之关联的引
用队列中。后续,我们可以调用 ReferenceQueue poll() 方法来检查是否有它所关心的
对象被回收。

 

       软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留对象,当内存不
足时就清理掉对象,这样就可以更好保证在使用缓存时,尽量不会耗尽内存。
4.3.3  弱引用:
       弱引用通过 WeakReference 类实现。当 GC 时,一旦发现了具有“弱引用”引用的对象,
不管当前内存空间足够与否,都可能会回收它的内存。由于垃圾回收器是一个优先级很低的
线程 ,因此不一 定会很快回收弱引用的对象 。 弱引用也可以和 一 个引用队列 (ReferenceQueue )联合使用,如果弱引用所引用的对象被垃圾回收, Java 虚拟机就会把
这个弱引用加入到与之关联的引用队列中。

 

 

弱引用同样可应用于内存敏感的缓存。
4.3.4 虚引用:
       虚引用也叫幻象引用,通过 PhantomReference 类对象来实现。无法通过虚引用访问对象
的任何属性或函数。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时
候都可能被垃圾回收器回收。虚引用必须和引用队列 ( ReferenceQueue )联合使用。当垃
圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这
个虚引用加入到与之关联的引用队列中。
       因为 PhantomReference 引用的是需要被垃圾回收的对象,所以在类的定义中, get 一直
都是返回 null
       程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾
回收。虚引用可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收
集器回收之前会收到一条系统通知。

 

 

4.4 缓存应用增强分析

4.4.1 为什么要使用缓存?
    降低数据库的访问压力,提高数据的查询效率,改善用户体验。
4.4.2 你了解哪些缓存?
1) 数据库内置的缓存? ( 例如 mysql 的查询缓存 )
2) 数据层缓存 ( 一般由持久层框架提供,例如 MyBatis)
3) 业务层缓存 ( 基于 map 等实现的本缓存,分布式缓存 - 例如 redis)
4) CPU 缓存 ( 高速缓冲区 )
5) 浏览器内置缓存?
4.4.3 设计缓存时你应该考虑哪些问题?
1) 存储结构 ( 使用什么结构存储数据效率会更高 ?- 散列表 )
2) 淘汰算法 ( 缓存容量有限 -LRU/FIFO/LFU)
3) 任务调度 ( 定期刷新缓存,缓存失效时间 )
4) 并发安全 ( 缓存并发访问时的线程安全 )
5) 日志记录 ( 缓存是否命中,命中率是多少 )
6) 序列化 ( 存对象时序列化、取对象时反序列化 )
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值