java中的内存回收机制所采用的算法,前端面试常考题:JS垃圾回收机制

return {o1: obj1,o2: obj2,}}

let obj = objGroup({name: 'obj1'}, {name: 'obj2'})console.log(obj)

上面的这个例子中,obj1和obj2通过各自的属性相互引用,所有它们的引用计数都不为零,这样就不会被垃圾回收机制回收,造成内存浪费。

引用计数算法其实还有一个比较大的缺点,就是我们需要单独拿出一片空间去维护每个变量的引用计数,这对于比较大的程序,空间开销还是比较大的。

引用计数算法优点:

引用计数为零时,发现垃圾立即回收;

最大限度减少程序暂停;

引用计数算法缺点:

无法回收循环引用的对象;

空间开销比较大;

标记清除(Mark-Sweep)

核心思想:分标记和清除两个阶段完成。

遍历所有对象找标记活动对象;

遍历所有对象清除没有标记对象;

回收相应的空间。

标记清除算法的优点是:对比引用计数算法,标记清除算法最大的优点是能够回收循环引用的对象,它也是v8引擎使用最多的算法。

标记清除算法的缺点是:

f4566d1394d4f9f43cd7576f5d1acebe.png

上图我们可以看到,红色区域是一个根对象,就是一个全局变量,会被标记;而蓝色区域就是没有被标记的对象,会被回收机制回收。这时就会出现一个问题,表面上蓝色区域被回收了三个空间,但是这三个空间是不连续的,当我们有一个需要三个空间的对象,那么我们刚刚被回收的空间是不能被分配的,这就是“空间碎片化”。

标记整理(Mark-Compact)

为了解决内存碎片化的问题,提高对内存的利用,引入了标记整理算法。

标记整理可以看做是标记清除的增强。标记阶段的操作和标记清除一致。

清除阶段会先执行整理,移动对象位置,将存活的对象移动到一边,然后再清理端边界外的内存。

0a6edea50b9827f0688dc395beae70bd.png

c1164d795b666aec9bfb23c1bbe072c7.png

33467af4327a3085f59da2e09831c1b0.png

标记整理的缺点是:移动对象位置,不会立即回收对象,回收的效率比较慢。

增量标记(Incremental Marking)

为了减少全停顿的时间,V8对标记进行了优化,将一次停顿进行的标记过程,分成了很多小步。每执行完一小步就让应用逻辑执行一会儿,这样交替多次后完成标记。

6a45ba6e8e681166cb8160161eec6e1e.png

长时间的GC,会导致应用暂停和无响应,将会导致糟糕的用户体验。从2011年起,v8就将「全暂停」标记换成了增量标记。改进后的标记方式,最大停顿时间减少到原来的1/6。

v8引擎垃圾回收策略

采用分代回收的思想;

内存分为新生代、老生代;

针对不同对象采用不同算法:

(1)新生代:对象的存活时间较短。新生对象或只经过一次垃圾回收的对象。

(2)老生代:对象存活时间较长。经历过一次或多次垃圾回收的对象。

8e192a524793778ee392c208d03d9ae6.png

V8堆的空间等于新生代空间加上老生代空间。且针对不同的操作系统对空间做了内存的限制。

55df9f03c716fe7dbc8cc36171aeff1d.png

限制内存的原因: 针对浏览器来说,这样的内存是足够使用的。针对浏览器的GC机制,经过不断的测试,如果内存再设置大一点,GC回收的时间就会达到用户的感知,会造成感知上的卡顿。

回收新生代对象

回收新生代对象主要采用复制算法(Scavenge 算法)加标记整理算法。而Scavenge 算法的具体实现,主要采用了Cheney算法。

08ee58d3ef97ce1d3b268d34493c7931.png

Cheney算法将内存分为两个等大空间,使用空间为From,空闲空间为To。

检查From空间内的存活对象,若对象存活,检查对象是否符合晋升条件,若符合条件则晋升到老生代,否则将对象从 From 空间复制到 To 空间。若对象不存活,则释放不存活对象的空间。完成复制后,将 From 空间与 To 空间进行角色翻转。

对象晋升机制

一轮GC还存活的新生代需要晋升。

当对象从From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置为25%的比例的原因是,当完成 Scavenge 回收后,To 空间将翻转成From 空间,继续进行对象内存的分配。若占比过大,将影响后续内存分配。

回收老生代对象

回收老生代对象主要采用标记清除、标记整理、增量标记算法,主要使用标记清除算法,只有在内存分配不足时,采用标记整理算法。

首先使用标记清除完成垃圾空间的回收;

采用标记整理进行空间优化;

采用增量标记进行效率优化;

新生代和老生代回收对比

新生代由于占用空间比较少,采用空间换时间机制。老生代区域空间比较大,不太适合大量的复制算法和标记整理,所以最常用的是标记清除算法,为了就是让全停顿的时间尽量减少。

我们先写一段比较消耗内存的代码:

< buttonclass= "btn">点击 button>< >constbtn = document.querySelector( '.btn')constarrList = []btn.onclick = function( ){for( leti = 0; i < 100000; i++) {constp = document.( 'p')// p.innerHTML = '我是一个p元素'document.body.(p)}arrList.push( newArray( 1000000).join( 'x'))} >

使用浏览器的Performance来监控内存变化

3bd812163610c968ee6b0a93464489d7.png

点击录制,然后我们操作们感觉消耗性能的操作,操作完成之后,点击stop停止录制。

e86fc63683bf90b58d0157ce21fbe28e.png

然后我们看一看是那些地方引起了内存的泄漏,我们只需要关注内存即可。

84c505a187110d2d6a4d3041dc7c5280.png

可以看到内存在短时间消耗的比较快,下降的小凹槽,就是浏览器在进行垃圾回收。

性能优化

1.避免使用全局变量

全局变量会挂载在window下;

全局变量至少有一个引用计数;

全局变量存活更久,持续占用内存;

在明确数据作用域的情况下,尽量使用局部变量;

2.减少判断层级

functiondoSomething( part, chapter){constparts = [ 'ES2016', '工程化', 'Vue', 'React', 'Node']if(part) {if(parts.includes(part)) {console.log( '属于当前课程')if(chapter > 5) {console.log( '您需要提供 VIP 身份')}}} else{console.log( '请确认模块信息')}}doSomething( 'Vue', 6)// 减少判断层级functiondoSomething( part, chapter){constparts = [ 'ES2016', '工程化', 'Vue', 'React', 'Node']if(!part) {console.log( '请确认模块信息')return}if(!parts.includes(part)) returnconsole.log( '属于当前课程')if(chapter > 5) {console.log( '您需要提供 VIP 身份')}}doSomething( 'Vue', 6)

3.减少数据读取次数

对于频繁使用的数据,我们要对数据进行缓存。

< divid= "skip"class= "skip"> div>< >varoBox = document.getElementById( 'skip')// function hasEle (ele, cls) {// return ele.className === cls// }functionhasEle( ele, cls){constclassName = ele.classNamereturnclassName === cls}console.log(hasEle(oBox, 'skip')) >

4.减少循环体中的活动

vartest = =>{varivararr = [ 'Hello World!', 25, '岂曰无衣,与子同袍']for(i = 0; i < arr.length; i++) {console.log(arr[i])}}// 优化后,将arr.length单独提出,防止每次循环都获取一次vartest = =>{varivararr = [ 'Hello World!', 25, '岂曰无衣,与子同袍']varlen = arr.lengthfor(i = 0; i < len; i++) {console.log(arr[i])}}

5.事件绑定优化

< ulclass= "ul">< li>Hello World! li>< li>25 li>< li>岂曰无衣,与子同袍 li> ul>< >varlist = document.querySelectorAll( 'li')functionshowTxt( ev){console.log(ev.target.innerHTML)}for(item oflist) {item.onclick = showTxt}// 优化后functionshowTxt( ev){vartarget = ev.targetif(target.nodeName.toLowerCase === 'li') {console.log(ev.target.innerHTML)}}varul = document.querySelector( '.ul')ul.addEventListener( 'click', showTxt) >< buttonclass= "btn">点击 button>< >functionfoo( ){letel = document.querySelector( '.btn')el.onclick = function( ){console.log(el.className)}}foo// 优化后functionfoo1( ){letel = document.querySelector( '.btn')el.onclick = function( ){console.log(el.className)}el = null// 将el置为 null 防止闭包中的引用使得不能被回收}foo1 >点 击“阅读原文”,进入华为云社区

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值