美团一面面经:Threadlocal(线程局部变量的原理)->内存泄漏问题->垃圾回收机制

首先我们需要知道什么是ThreadLocal :

ThreadLocal是java的一个类,为每一个线程中创建一个单独的变量副本,每个线程只可以访问自己的副本变量。我们通过使用ThreadLocal这种机制来保证线程安全性。

在我们的ThreadLocal类有一个静态内部类,叫做ThreadLocalMap,用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。

我们的ThreadLocal本身并不存储值,它只是作为一个key保存到ThreadLocalMap中,但是它作为一个Key采用的是弱引用而非强引用

此处我们首先要明白什么是弱引用:

弱引用:不会阻止垃圾回收它所指向的对象。

强引用:保证对象的存活,使其不被垃圾机制回收。

我们在知道概念后,就很自然的能想到 我们的key很可能在GC的时候被回收掉。造成内存泄漏的问题

什么是内存泄漏呢?

就拿这里的列子来说:我们ThreadLocalMap中由于key被GC回收后,存在key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本事是不会清除的。如果没有手动删除对应的key就会导致这些内存不会回收也无法访问。这就是内存泄漏

我们如写解决呢?

通常我们会在使用完ThreadLocal中调用remove方法。那么我们应该如何调用呢?

以本人的代码为例,我们可以写在拦截器的afterCompletion方法中。

那么问题又来了我们的afterCompletion是的执行时机是什么时刻呢?

我们知道afterCompletion是用于处理HTTP请求之后执行的。它是一个处理器适配器(HandlerAdapter)的实现类。它生命周期的在于视图渲染完成并且响应已经提交给客户端之后执行的,也就是前端控制器响应浏览器之后执行的。

注意:在不使用线程池的前提下,即使不调用remove方法,线程的“变量副本”也会被gc回收,既不会造成内存泄漏的情况。

因为:如果每个请求都由一个新的线程处理,那么请求处理完成后,线程会自然地终止,线程局部变量(ThreadLocal)所引用的对象也会随着线程的销毁而被GC回收。

那么什么是垃圾回收机制呢?

JVM的垃圾回收动作可以大致分为两大步,首先是【如何发现垃圾】,然后是【如何回收垃圾】。

1.如何发现垃圾?

当下主流的是根搜索算法也叫可达性分析

把所有的引用关系看作是一张图,从一个节点GC ROOT(线程栈的本地变量、静态变量、本地方法栈的变量等等)开始,寻找对应的引用节点,找到这个节点后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后。剩余的节点则被认为是没有被引用到的节点,既可以当作垃圾。

2.如何回收垃圾?

java中用于【回收垃圾】的常见算法有4种:

1.标记-清除算法(mark and sweep)

分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成之后同一回收掉所有被标记的对象。

缺点:首先,效率问题,标记和清除效率都不高。其次,标记清除之后会产生大量的不联系的内存碎片

2.标记-整理算法

在标记清除算法基础上做了改进,标记阶段相同。但标记完成后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫整理

优点:内存被整理后不会产生大量不连续内存碎片。

3.复制算法

将可用内存按容量分为大小相等的两块,每次只能使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。

缺点:可使用内存只有原来的一半

4.分代收集算法

当前主流JVM都采用分代收集算法,这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代、永久代(1.8后废弃),不同生命周期的对象可以采取不同的回收算法,以便提高回收效率

(1)年轻代(复制算法)

  1. 所有新生成的的对象首先都是放在年轻代
  2. 新生代内存按照8:1:1的比例分为了一个Eden区和两个Survivor(survivor0,survivor1)区、也叫survivor区。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到survivor0区,然后清空eden区,当这个survivor0区也存满时,则将eden区和survivor0区存活的对象复制到另一个survivor1区,然后清空eden区和幸存者0区,此时survivor0区就是空的,然后将survivor0区和survivor1区交换内存,则保持survivor1区为空,如此循环操作。
  3. 当survivor1区不足以存放eden区和survivor0区的存活对象的时候,就将存活对象直接放到老年代,若是老年代也满了就会触发一次Major GC,也就是新生代和老年代都进行垃圾回收。
  4. 新生代发送的GC叫Minor GC ,MinorGc发送频率比较高(不一定等Eden区满了才触发)

(3)永久代 JDK1.8后废除 被本地内存中的元空间取代

用于存放静态文件,如java类,方法等JDK1.8后存放到了元数据区,常量池存在堆里。永久代对垃圾回收没有显著影响。

美团一面给我的感受是面试官人很好,不愧是大企业 第一次面试有这样的体验非常不错,问题循环渐进 问的比较细 可能是在看你是否真的理解了

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值