1. 背景:以前拿着自己的简历去面试的时候, 被问过堆栈的问题, 就查了下资料, 一知半解,背了一下. 现在作为一面面试官, 上头安排面别人,并且领导说二面问到了堆栈相关的,于是就在昨天夜里,月黑风高,心血来潮, 拆解了一下 栈
  2. 一些零碎的知识点:方法栈,线程栈,后进先出,StackOverFlow栈溢出
  3. 拆解 后进先出,网上查了栈这个汉字的本意, 最最原始的意思是"古代用竹木条横排编成车箱的轻便车子",作为黄淮地区农村人,大概就是类似"平车"一样的东西了.简单理解就是 那块平板就是栈,加上轮子就是车.平板大概是 两根竹子,每根都是一头绑住,然后劈开直径,拿竹片塞到劈开的缝隙,塞到差不多长度后就再稍微绑一下. 那么要取其中的竹片,只能从 "稍微绑一下"的这头一个一个取,也就是后进先出了.牵强了点,就酱.
  4. 拆解 方法栈,线程栈, 按照上面的解释, 方法栈就是一片竹片,里面放着方法用到的内存,方法用到的内存基本就是基本数据的值 + 对象引用的地址.所以才不需要多大空间.一个个方法的调用,就让调用链有了深度,就像 一个个竹片塞到竹缝隙里,所以, 就能理解到 线程栈和方法栈是一对多的关系.
  5. 拆解StackOverFlow,首先内存是有大小限制的,堆内存也好占内存也罢,操作系统给jvm进程一个内存大小,jvm给线程栈设置一个最大的内存大小,栈内存不够就有了StackOverFlow,也就是缝隙塞满了竹片,下一个竹片就像给水杯倒水一样"溢出了".
  6. 再拆解StackOverFlow, 根据5,基本的概念了解了.那么,是什么导致栈溢出呢, 查资料会发现,递归深度过深灰导致栈溢出.那么整体的逻辑就是, 某个线程执行到递归时,第一次执行, 把当前执行的递归方法里的变量做一个快照,放到一个栈帧(竹片)里,压入栈,然后再次调用该方法(递归了开始),又生成一个栈帧,再递归,就把上一个栈帧压入栈,然后生成一个新的栈帧.循环下去,当最后一次调用该方法,并返回后,该方法对应的栈帧就从 栈顶 去除并释放.然后依次释放后面的栈帧.那么,如果递归次数过多,就会导致超过配置的线程栈内存大小,就会StackOverFlow了.
  7. 6的证据:在我们e.printStackTrace()的时候,看打印的内容,会发现它打印了从报错方法到thread.run的所有经过的方法,也就是调用链, 这也是栈帧(方法栈)是以栈的数据结构存储这些方法临时数据的证据.在递归时,当前方法访问不到上一个同名方法里的变量, 原因就是它的访问范围限制在当前栈帧里.
  8. 思考StackOverFlow,大学时,做过一个题目,大概是已知入栈的顺序是12345,下列哪个不是可能得出栈顺序.这个题目的精髓就是方法栈帧 在 线程的栈上的进出规则了.比如先入123,此时可以取出3,也可以再取出2和1,但是不能直接取出2.而12345这些数字,就是所谓的"栈帧"了,也是方法栈.
  9. 就这?就这.有的文章说,方法要小,也就是代码行数少,一个大方法,最好是一个在这个方法控制整体流程,然后用小方法做各个细节逻辑.也就是大任务拆成小任务.之前一直不理解为啥,有的人说是为了可读性,但我觉得不全是,对熟悉业务逻辑的人来说,可读性更好了,因为你知道小方法里干了啥.但对不熟悉业务的人来说,比如交接不严谨的团队,熟悉代码就是在方法中跳来跳去,还不如一个大方法可读性好.但如果从栈内存优化方面来考虑,小方法就是顺理成章的存在了.假设一个大方法占用1m内存,假设设置的占内存是900k,那么直接运行这个大方法会栈溢出.把大方法拆小,就能拿立刻回收掉结束的小方法的栈内存,然后重复利用,就不会栈溢出.

站在巨人的肩上
2 声望0 粉丝