热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java全局变量内存不回收_JavaScript编译原理与内存管理

编译原理编译还是解释?编程语言分为编译型语言和解释型语言两种,编译型语言的源代码在执行之前要进行完全编译,例如Java,如果
d867b41cfbbde18143f05457bd065843.png

编译原理

编译还是解释?

编程语言分为编译型语言和解释型语言两种,编译型语言的源代码在执行之前要进行完全编译,例如 Java ,如果要运行,就需要 Java 虚拟机( JVM )把源代码转换为具体平台上的机器指令去执行。而解释型语言,一边解释一边执行,很明显执行速度会慢于编译型语言。 鉴于 Javascript 在前端执行,并且只是做一些简单的表单验证等工作,所以,长期以来,市面上的浏览器引擎都是将 Javascript 作为解释型语言去运行。然而,在过去的几年,Javascript 受到了更加广泛的应用,引擎对 Javascript 的处理速度也变得更加重要。谷歌的 V8 引擎就是为解决这一问题而诞生的。
V8 引擎引入了 Java 虚拟机技术,在代码执行前,将源代码编译生成机器码,并使用隐藏类、内联缓存等技术来提高性能,从而让 Javascript 在 V8 引擎下的运行速度大幅度提高。与传统编译型语言不同的是,Javascript 的编译工作发生在运行时,也就是代码执行前的几微妙时间内,而且代码并非一次性完全编译,而是在某些代码需要执行时,才会进行编译。
我写的整个" 深入挖掘系列 “手记,都是基于 V8 引擎的,因此很多理论与其他引擎的” 运行时 "理论会略有不同。希望此系列手记,可以起到抛砖引玉的作用。

隐藏类和内联缓存

根据 V8 引擎的运行时理论,代码在编译阶段,最主要的工作就是确定标识符的位置( 作用域链 )和类型( 原型链 ),等到代码执行时,不再需要进行额外的查找,几个机器指令即可完成,节省了大量时间。但与 Java 这种静态类型语言稍有不同,Javascript 是一种动态类型语言,即标识符的数据类型在编译阶段无法确定。大多数 Javascript 引擎选择通过字符串匹配来查找属性值,每一次访问某个属性,都需要重新找到其在内存中的位置。如果属性值存取十分频繁,会严重影响性能。为了突破 Javascript 这种天生的缺陷,V8 引擎采用了隐藏类和内联缓存相结合的机制。这种机制将那些具有相同属性名的对象划归为一类,并把初次查找的属性值缓存起来,当下次查找的时候,优先比较当前对象是否是之前的隐藏类,如果是,就直接使用,如果不是,就再创建一个隐藏类,并缓存起来。

内存管理

内存分配

*以下内容,对象默认包括 Number、Boolean 和 String 这三种包装对象。

首先,V8 引擎在内存使用上,针对不同的操作系统做了相应的限制:32位操作系统约为700M,64为操作系统约为1.4G。 其次,V8 引擎在内存划分上大致可以分为栈内存和堆内存:栈的优势是,存取速度比堆要快,但缺点是,存在栈中的数据大小与生命周期必须是确定的;堆的优势是,可以动态分配内存,数据的生命周期也没有限制,但缺点是,由于要在运行时动态分配内存,存取速度较慢。堆是垃圾收集器活动的区域。

栈内存

在 V8 引擎中,大部分的对象都是直接创建在堆上。但是,如果确定一个对象不会逃逸出方法外( 数据大小与生命周期确定 ),那就让这个对象在栈上分配内存,对象占用的内存也会随着方法的销毁而销毁。保存在栈上,从而减少了临时对象在堆上的分配数量。

堆内存

V8 引擎将堆内存细分为几个不同功能的空间:

  1. 新生代内存区:新创建的对象被分配到这里,属于临时区域。
  2. 老生代内存区:临时区域的对象达到一定的生存率之后,就会被分配到这个区域,主要包括引用值和仍然在使用的基本值( 闭包或全局 )。
  3. 大对象内存区:当数据需要 1M 以上的空间,体积较大,就会被分配到这个区域。

如果再细分,新生代内存区又被平分为两部分,From 区和 To 区,任意时刻只有一个区域的内存被使用。下面" 内存回收 "会详细讲解。其实,无论如何细分,目的只有一个:更快的分配内存和回收内存。

内存回收

回收范围

栈内存不需要进行内存回收,因为存在栈中的数据会随着局部环境的销毁而自动清除,因此只有堆需要内存回收。

如何判断对象不再使用

某个对象已经离开执行环境,并且不再被任何变量引用,就可将其占用的空间释放。

内存回收常用算法

1、标记清除

标记清除是目前主流的内存回收的算法,主要分为两个阶段:标记阶段和清除阶段。这种算法的思想是标记不再使用的对象,然后在清除阶段回收其内存。

2、标记整理

标记整理首先对所有对象进行一次标记,将还在使用的对象压缩到内存的一端,之后,直接清理掉不再使用的内存。

3、复制算法

复制算法将现有的内存空间平分为两部分,每次只使用其中一部分,假设为 From 区和 To 区。创建对象时,会在 From 区分配内存,当垃圾收集器运行时,如果 From 区的对象还在使用,就会被复制到 To 区( 数据体积超过 1M 以上,会直接进入大对象内存区 ),之后, From 区的内存可以直接释放。这一系列完成后,From 区和 To 区会进行角色互换,继续下一次内存回收。

垃圾收集器

Javascript 是具有自动垃圾收集机制的编程语言,内存分配与内存回收完全是自动化管理。自动垃圾收集机制的最大特点是按固定时间间隔,周期性的执行内存回收。但 Javascript 的垃圾收集器也有自己的特点:首先,Javascript 是单线程语言,因此垃圾收集器是串行内存回收,而多线程语言大多是并行内存回收;其次,垃圾收集器在运行时,会独占 CPU,主线程暂停执行。长时间暂停必然影响用户体验,因此 V8 引擎引入了" 标记增量 "的概念,即,将原本一口气完成的内存回收,分步完成,与主线程程序交替进行。

V8 引擎的分代式垃圾回收机制

这种机制将堆内存分为新生代和老生代,并针对两种内存执行不同的算法。

  1. 新生代内存:串行 - 复制算法,并独占 CPU,垃圾收集器运行最为频繁。
  2. 老生代内存:串行 - 标记清除 - 标记整理,并独占 CPU。
  3. 大对象内存区也属于老生代,因此同样适用老生代内存回收的算法,垃圾收集器运行最为不频繁。

内存泄漏

不再使用的内存,没有得到及时释放,就叫做内存泄漏。

常见的内存泄漏

1、意外的全局变量

在函数内部没有使用 var 关键字声明变量或者 this 意外绑定 window ,会创建全局变量,造成内存泄漏。

例子:

function fn() {this.name = "Tom";age = 20;
}
fn();
console.log(name); //输出:Tom
console.log(age); //输出:20

*全局变量一直处于全局环境,内存不会释放,建议不要储存过大的数据。

2、闭包

闭包会使一些变量无法被及时销毁,造成内存泄漏。

例子:

function fn() {var n = 999;return function() {console.log(n);}
}
var f = fn();
f(); //输出:999

*关于闭包是否会造成内存泄漏,一直有争议。个人认为,内存泄露并非闭包的问题。我们会选择主动把一些变量封闭在闭包中,主要考虑的是在不污染全局环境的前提下,以后还会需要使用这些变量,这些变量占用的内存并非是没有用的。所以,闭包并不符合内存泄漏的定义。

3、引用计数垃圾收集机制

引用计数的主要思想是跟踪对象被引用的次数,目前主流引擎都不再使用这种机制。它最大的缺陷是容易造成循环引用,导致内存得不到回收。

*由于 IE9 之前的浏览器采用引用计数垃圾收集机制,所以非常容易出现内存泄漏的问题,但目前的浏览器早已抛弃这种过时的算法,那些早期 IE 浏览器特有的内存泄漏的例子,这里就不再叙述了。

如有错误,欢迎指正,本人不胜感激。


作者:为爱心太软



推荐阅读
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
author-avatar
尼姆了_960
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有