logo头像

分享技术,品味人生

Java核心基础之方法引用

近期在研究SpringMVC和MybatisPlus组合项目,对于国内java架构常用所有表添加统一字段的统一处理场景,采用AOP拦截器如何在无Request对象时获得session数据中,采用ThreadLocal内存本地对象解决方案,忽然意识到高并发场景极有可能发生内存泄漏,因此有了本篇文章。

说到内存泄漏,这里不得不提Java的内存回收,GC算法。

关于GC

GC也就是JVM Garbage Collectors的缩写,做过c/cpp的小伙伴都知道内存管理是很麻烦的,所以本章我们将简单介绍下jvm的内存结构,GC的原理和算法!这里参考资料来源:一文看懂JVM内存布局及GC原理_技术管理_杨俊明_InfoQ精选文章

JVM运行时内存布局

来自Java8的[虚拟机规范](Chapter 2. The Structure of the Java Virtual Machine (oracle.com))用语Run-Time Data Areas

image-20220907095649931

GC内存回收主要战场,集中在上图三个区域

  • Heap:GC 垃圾回收的主站场,存放类的实例对象及 Arrays 实例等
  • Method Area:方法区,存放类结构、类成员定义,static 静态成员等。
  • Runtime Constant Pool:运行时常量池,比如:字符串,int -128~127 范围的值等

Heap、Method Area 都是在虚拟机启动时创建,虚拟机退出时释放。

GC垃圾回收原理

垃圾判断算法主要有引用计数可达性分析两种,引用计数法虽然简单易用,但是碰到交叉引用就不灵了,目前主流JVM都采用可达性分析为主!

image-20220907100154895

那么问题来了,那些内存需要回收?

img

垃圾回收算法

常见四种算法,都不完美,最后一种复杂但使用,类似磁盘碎片整理,详细来看看

1)、mark-sweep 标记清除法

img

从图上可知,对于垃圾内存进行了标记删除,产生了很多碎片!

2)、mark-copy 标记复制法

img

简单粗暴的55分法,对于内存充裕,标记完快速搬迁,效果很明显,缺点也同样明显,那就是内存要充裕,但现实往往是内存最紧张的时候来进行GC啊。

3)、mark-compact 标记-整理(也称标记-压缩)法

img

标记整理综合上面两种算法的优缺点,唯一不足的是效率不高

4)、generation-collect 分代收集算法

上述三种算法,每种都有各自的优缺点,都不完美。现代 JVM 中,往往是综合使用的,经过大量实际分析发现大部分对象其实相当短命,很少有对象能在 GC 后活下来。因此诞生了分代的思想,以 Hotspot 为例(JDK 7):

img

内存分成了三大块:年青代(Young Genaration),老年代(Old Generation),永久代(Permanent Generation),其中 Young Genaration 更是又细为分 eden,S0,S1 三个区。

img

结合我们经常使用的一些 jvm 调优参数后,一些参数能影响的各区域内存大小值,具体暂时不展开,下面我们来深究下垃圾判断算法的四种引用类型。

Java四大引用类型

java的内存分配和回收都交给JVM统一处理啦,回收的基础是提前做了引用区分,给可达性分析提供了基础!

根据优先级高低分别为:强引用、软引用、弱引用、虚引用。

强引用

强引用是非常普遍的引用类型,我们常见、常写的代码几乎都是,如下:

Object o = new Object;

强引用分配出去的内存,哪怕是栈溢出、堆溢出都不会被GC进程回收,除非做了这个操作 o = null;

这里我们做个实验

public class Student {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Student 被回收了");
    }
}

public static void main(String[] args) {
        Student student = new Student();
        student = null;
        System.gc();
}

重写finalize方法仅用于实验,实际场景不适合。

强引用特点:jvm宁可内存溢出也不回收,除非显式置为NULL!

软引用

软引用特点:GC后也不强制回收,除非内存真不足。

实验:

SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());

byte[] bytes = new byte[1024 * 1024 * 5];
System.out.println(softReference.get());

执行结果:

E:\Java\jdk1.8.0_121\bin\java.exe -Xmx20M -javaagent:C:\java\IntelliJIDEA2022.1.3\lib\idea_rt.jar=4771:C:\Java\IntelliJIDEA2022.1.3\bin -Dfile.encoding=UTF-8 -classpath 

[B@4783da3f
[B@4783da3f
null

这里可以提前在idea的运行配置设定内存为20M,当内存使用超过75%时即可触发!

image-20220907111001916

弱引用

弱引用特点:GC后立刻回收

实验:

WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());

执行结果:

[B@4783da3f
null

虚引用

虚引用特点:强制标记回收

实验:

ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
System.out.println(reference.get());

执行结果:

null

分析:

get方法直接返回null,且虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。应用queue提交回收通知,应用场景比较特殊,主要用于NIO时堆外内存的管理!

絮叨

JVM的内存管理技术让很大程度上让java的发展远远超过c/CPP,了解jvm内存结构、gc算法、四大引用类型绝对能让编程事业锦上添花!

评论系统未开启,无法评论!