java程序员必须知道的内存知识-应用层
1.volatile
可见性,使用volatile修饰的变量可以立刻被其它线程读取到,经常会被用到多线程同步的关键变量上,像aqs的state。
因为CPU在访问主存需要大约十几个时钟周期,为了提高cpu的效率便有了高速缓存,当数据被加载到高速缓存时,其它核并不能第一时间看到。
内存屏障,最常见的就是双检锁了,我们简单的new对象在虚拟机内部其实需要很多操作,虚拟机为了提高性能,会对我们代码进行重排,使用volatile可以保证变量在被编译时的顺序性。
volatile、synchronized、final都会影响虚拟机的指令重排,会通过指令集中的loadload、storestore、loadstore、storeload四个内存屏障实现。
总结来说,volatile的作用有3个,编译重排(虚拟机优化重排)、指令重排(cpu指令重排)、内存重排(高速缓存脏读)
2.缓存行
缓存行是为了解决cpu访问主存时间长的问题,之前文章有过介绍。
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
缓存行友好
因为缓存行会存储多份数据,所以有了缓存行一致性协议,但一致性协议有一定成本,如果缓存行被共享,又频繁修改,会导致性能下降。
解决办法也很简单,就是让一个缓存行只有一条数据,保证数据独享。
java8提供了jdk.internal.vm.annotation.Contended注解,在类上加注解后,虚拟机会自动做缓存行填充。
缓存行抖动
因为缓存行大小有限,所以缓存行只能缓存部分数据,因为缓存行采用映射的方式选择缓存的数据,如下图,如果ABCD四个数据都映射到上面的一个行里,当我们要访问A时,要把A加载进来,这时我们要访问B,要把缓存行清空,再加载B,这时如果还需要访问A,又得把B清掉,加载A,这就是缓存行抖动。
这是一个常见的例子,数组x和数组y同时映射到一个缓存行,当访问x[i]时,要加载x到缓存行,访问y[i]时又需要把y加载到缓存行,常见的解决办法也比较简单,就是扩充数组大小,让x和y无法映射到一个缓存行。
3.内存池
堆内存由jvm替我们申请和回收,但是垃圾回收也是我们系统的瓶颈之一,所以有些时候为了提高性能或者其他原因,我们也会使用堆外内存,例如netty,netty的堆外内存池使用的就是类似slab的机制实现的,如果有兴趣可以看看源码,这里就不细说了。
4.java引用
强引用
强引用就是我们平时使用的Object a = new Object()。如果一个对象具有强引用,那垃圾回收器就不会回收这个new Object()
软引用
如果一个对象只有软引用,在内存充足时垃圾回收器就不会回收它;如果内存空间不足了,就会回收软引用应用的对象。
软引用的回收会根据上次gc剩余内存,软引用上次访问的时间动态调整,就是上次访问的时间越久,上次gc剩余内存越少,越容易被回收。
弱引用
如果一个对象只有弱引用,只要触发gc就会被回收(包括年轻代gc)。
虚引用
虚引用的get方***直接返回null,虚引用的作用主要是对象在被回收时可以通过虚引用通知到程序,对象被回收了。