跳至主要內容

其他

LincZero大约 15 分钟

其他

对象引用和裸指针

内存管理机制

Python的内存盒子

Python(强类型语言)

  • 一张变量表:每条数据的形式:(变量名 —— 变量地址 )
  • 一个地址内存空间:每个盒子的内容:(变量 —— 变量类型)
  • 赋值时的情况:修改变量表中的变量地址,这个变量地址所代表的盒子的类型可以与原来不同

C/C++(弱类型语言)

  • 一张变量名表,每条数据的形式:(变量名 —— 变量地址 —— 变量类型)
  • 一个地址内存空间:每个盒子的内容:
  • 赋值时的情况:

各语言vs虚拟内存 (GPT)

他们是否拥有相同的虚拟内存?虚拟内存都是由计算机系统统一分配给每个程序进程的吧,无论是什么语言的程序


是的,你完全正确。虚拟内存是由操作系统提供的一种内存管理技术,它为每个进程提供了一个独立的、连续的地址空间。这个地址空间被分割成多个块,每个块被称为一个页面。当一个进程需要使用一块内存时,操作系统会将一个物理内存块分配给这个页面。如果物理内存不足,操作系统可以将一些页面移动到硬盘上,这就是所谓的页面交换。

虚拟内存的管理是由操作系统完成的,与编程语言无关。无论你的程序是用C++、Java、Python还是任何其他语言编写的,都会使用到虚拟内存。虚拟内存的主要优点是,它使每个进程都认为自己独占了全部的内存,这大大简化了内存管理。

然而,虽然所有的进程都使用虚拟内存,但不同的编程语言可能会以不同的方式使用它。例如,C++允许程序员直接管理内存,这意味着程序员可以决定何时申请和释放内存。另一方面,Java和Python等语言提供了自动内存管理,这意味着内存的分配和回收是由语言的运行时系统自动完成的。

所以,虽然所有的程序都使用虚拟内存,但是不同的语言可能会以不同的方式管理和使用这些内存。


你说:无论你的程序是用C++、Java、Python还是任何其他语言编写的,都会使用到同一个虚拟内存。

C/C++编译出来的可执行程序使用虚拟内存可以理解。

Java这种虽然使用了虚拟机,但本质还是通过jvm转化成机器语言执行的也可以理解。

但是像python这种解释型语言、还有js这种有沙盒环境的语言,用的也是同一套虚拟内存?他们的运行环境在解释时会另外为执行的python程序或js程序时会另外向计算机系统申请进程并分配吗?还是说只是运行环境分配一部分空间出来给被运行的脚本使用?


在操作系统中,每个进程都有自己的虚拟内存空间,这是由操作系统提供的抽象概念,它使得每个进程都认为自己是在使用全部的(虚拟)内存。实际上,这些虚拟内存地址在需要的时候才会被映射到物理内存上。这种机制使得每个进程都能够在自己的地址空间中独立运行,而不会干扰其他进程。虚拟内存还提供了一种方便的方法来管理内存,例如页面交换(paging)和内存保护。

对于C++和Java程序,你的理解是正确的。C++程序直接编译为机器代码,运行时直接在其分配的虚拟内存空间中执行。Java程序通过JVM运行,JVM作为一个进程,也有自己的虚拟内存空间,Java代码在JVM中执行,使用的也是JVM的虚拟内存。

对于解释型语言如Python和JS,虽然它们的运行方式与C++和Java有所不同,但是它们的运行环境(如Python解释器或JS的运行环境如浏览器或Node.js)同样作为进程运行在操作系统上,也有自己的虚拟内存空间。当你运行一个Python或JS程序时,它们的代码会在这些运行环境中被解释执行,所使用的内存就是这些运行环境的虚拟内存。

总的来说,无论是编译型语言还是解释型语言,它们的代码最终都是在某个进程的虚拟内存空间中执行的。这个进程可能是直接运行机器代码的进程(如C++程序),也可能是提供运行环境的进程(如JVM、Python解释器或JS运行环境)。


解释器本身是单独的进程这我淡然知道,但我想问的是,例如python解释器使用了一个进程,那他在解释test.py时,是否是另外创建一个test.py对应的进程。而test.py中所创建的堆栈资源则使用新创建出来的那些资源,而非解释器所在的线程自身的资源 (虚拟内存)


当你运行一个Python脚本,如test.py时,Python解释器并不会为这个脚本创建一个新的进程。相反,test.py脚本的执行会在启动它的Python解释器进程内进行。这意味着test.py中所有的变量、函数、对象等都会在Python解释器进程的虚拟内存空间中创建和管理

Python解释器会为每个脚本或者交互式会话创建一个新的执行环境,这个执行环境包含了执行该脚本所需要的所有资源,如全局变量、函数和类的定义等。但这个执行环境并不对应一个操作系统级别的进程或线程,它只是在Python解释器进程内部的一个抽象概念。

如果你想要在一个新的进程中运行Python脚本,你需要显式地创建一个新的进程。Python的multiprocessing模块提供了这样的功能,你可以用它来创建和管理进程。在新的进程中运行的Python脚本会有自己的虚拟内存空间,与启动它的Python解释器进程的虚拟内存空间是隔离的。

同样的,JavaScript的执行也类似。例如,在浏览器中,每个标签页(包含一个或多个JavaScript脚本)通常对应一个浏览器进程(这取决于浏览器的具体实现),所有的JavaScript代码都在这个进程的虚拟内存空间中执行。如果你想要在一个新的进程中运行JavaScript代码,你需要使用特定的API,如Web Workers或Node.js的child_process模块。

现代C++的内存模型

参考:https://www.bilibili.com/video/BV1fN411u7wX(有点复杂,比较难,建议也等我对编译原理比较熟悉了再来看)

前提知识:

  1. 顺序一致 (Sequential Consistency),需要严格按代码顺序生成和执行
    • TSO模型,宽松一点,允许某些程度的重排 (Reordering),太严格会影响编译器优化
    • 现代C++/Java 使用 SC-DRF (Sequential Consistency for data race free) 模型
  2. Happens-before
    • 按代码顺序,若语句A在语句B之前,则:语句A Happens Before 语句B
  3. Synchronizes-with
    • 用于线程间的同步,通常借助原子变量的操作来实现

GPT

介绍和比较各个语言的内存管理模型:先重点比较:C++、Java、Python


内存管理是编程语言的一个重要特性,它决定了如何分配、使用和释放内存。让我们来看看C++、Java和Python这三种语言的内存管理模型。

  • C++
    • GC:C++的内存管理主要依赖于程序员。它提供了newdelete操作符来分配和释放内存。这为程序员提供了极大的灵活性,可以精确控制何时和如何使用内存。但是,这也意味着程序员需要对内存管理有深入的理解,并且需要小心避免内存泄漏和其他相关问题。
    • 内存区:分为四个区域:栈区(存放函数的参数值、局部变量值等)、堆区(动态分配内存)、全局/静态存储区(存放全局变量和静态变量)、常量存储区(存放常量,不允许修改)。
  • Java
    • GC:Java的内存管理相对自动化,它使用垃圾收集器自动回收不再使用的内存,这极大地减轻了程序员的负担。
    • 内存区:Java的内存分为堆、栈、方法区和程序计数器四个部分。其中: 堆是用于存放所有的对象实例,栈用于存放基本类型和对象引用。 方法区主要存放类信息,常量,静态变量等数据。 程序计数器用于记录线程执行的行号。 Java的垃圾收集器主要工作在堆区,当对象没有引用指向时,就可以被视为垃圾,等待回收。
  • Python
    • GC:Python也使用自动内存管理,它的内存管理机制包括引用计数、垃圾回收和内存池机制。引用计数是其主要的垃圾回收技术,每个对象都有一个引用计数,当引用计数降为0时,对象就会被删除。但是,这种方法无法处理循环引用的情况,因此Python还有一个垃圾回收器来检测并回收循环引用的对象。Python的内存池机制是为了提高内存利用率和性能。

比较

  • 控制程度:C++提供了最高的内存控制程度,但也需要程序员承担更多的责任。而Java和Python则提供了自动内存管理,使程序员可以更专注于程序逻辑。
  • 性能:C++的内存管理通常可以达到更高的性能,因为它避免了垃圾收集器带来的开销。但是,不正确的内存管理也可能导致性能问题。Java和Python的性能可能会因为垃圾收集而受到影响,但是在许多情况下,这种影响是可以接受的。
  • 安全性:Java和Python的自动内存管理可以避免很多因为错误的内存操作而导致的问题,如内存泄漏和野指针。而在C++中,这些问题需要程序员自己来避免。
  • 复杂性:C++的内存管理是最复杂的,需要程序员理解并正确处理各种内存相关的问题。Java和Python的内存管理对程序员来说相对简单,但是如果需要进行性能优化,还是需要理解其内存管理机制的。

这些我都知道。我主要指内存分配和赋值方面的比较。例如分配内存在什么位置、分配的内存是否会根据不同的基础类型分配在不同的区域,Python的变量赋值本质是变量表和内存盒子,赋值是修改表中的变量地址。其他语言有类似的机制吗。C++变量定义但未初始化时只有虚拟内存没有实际物理内存,其他语言也是这样吗


好的,我明白你的问题了。让我们更深入地探讨这些语言的内存分配和赋值机制。

  • C++
    • 在C++中,内存分配的位置取决于变量的类型和生命周期。自动(局部)变量通常分配在栈上,而使用new操作符创建的动态变量则分配在堆上。全局变量和静态变量则存储在全局/静态存储区。C++不会根据基础类型的不同分配在不同的区域,所有类型的变量都可以在堆上或栈上分配,这取决于变量的定义方式。
    • 对于赋值,C++的赋值操作通常是直接将一个值复制到变量所占的内存中。如果赋值操作涉及到对象,那么可能会调用对象的拷贝构造函数或赋值运算符。
    • 在C++中,定义变量但未初始化时,该变量确实会被分配虚拟内存,但不会被分配实际的物理内存。只有当变量被初始化时,才会分配物理内存。
  • Java
    • Java的内存分配也取决于变量的类型和生命周期。基本类型的局部变量和引用类型的引用都存储在栈上,而对象和数组则存储在堆上。全局变量(即类的静态成员变量)存储在方法区。
    • 在Java中,赋值操作通常是复制和传递值。对于基本类型,赋值操作会复制变量的值。对于对象,赋值操作会复制引用,而不是对象本身。这意味着,如果你修改了一个引用变量所引用的对象,那么所有引用这个对象的变量都会看到这个变化。
    • 在Java中,当你声明一个变量但不初始化时,基本类型的局部变量不会被赋予默认值,而对象的成员变量和数组会被赋予默认值。对于局部变量,如果你试图在初始化之前使用它,编译器会给出错误。
  • Python
    • Python的内存分配比较特殊,所有的变量都是对象,并且都存储在堆上。Python的变量实际上是对象的引用,当你赋值时,你实际上是在创建一个新的引用,指向同一个对象。
    • Python的赋值操作是将一个对象的引用赋给一个变量。如果一个对象没有任何引用指向它,那么它就会被垃圾收集器回收。
    • 在Python中,变量必须在使用之前被初始化,否则会引发一个错误。这是因为Python的变量实际上是对象的引用,如果一个变量没有被初始化,那么它就没有引用任何对象。

总结一下,这三种语言在内存分配和赋值机制上都有各自的特点。

  • C++提供了最大的灵活性,但也需要程序员承担更多的责任。
  • Java和Python提供了自动内存管理,使程序员可以更专注于程序逻辑。
  • Python的变量赋值机制和Java类似,都是引用赋值,而C++则更倾向于值赋值(但也支持引用和指针)

在变量定义但未初始化时,C++会分配虚拟内存,Java会对成员变量和数组赋默认值,Python则要求变量在使用前必须初始化。

垃圾回收机制

  • 无GC
    • C/C++
    • Rust:无GC
  • 有GC
    • JavaScript:使用new创建对象,但不用担心释放内存,垃圾信息收集器会自动处理这件事。详细原理去查Mozilla文档
    • Qt C++:一定程度简化了C++的垃圾回收机制,如对象树机制:详情略、自主去查
    • Java:相比C++一定程度简化了垃圾回收机制
    • Python
    • Go

性能

现代编程语言影响性能“最大”的因素,除了这两个:

  1. 执行机制,机器码>>虚拟机>>解释器
    • 机器码:C++、Rust、Go
    • 虚拟机:Java (JVM)
    • 解释器:JavaScript、Python (也可JIT)
  2. 无GC>>有GC
    • 无GC:C++、Rust
    • 有GC:Java、Go、JavaScript、Python

还有

  1. 数据结构和算法的使用:适当的数据结构和算法可以显著提高性能,反之则会拖慢性能。

  2. 并发和并行处理:并发和并行处理能力对于利用现代多核处理器架构的性能至关重要。语言如何支持并发和并行,会影响执行效率。

    • Python:有GIL,不佳,听说后面的改版能去掉
    • Go:语言级并发、异步IO
    • Java:优秀
    • C++:异步支持尚不足
    • Rust:优秀且安全
    • Js:异步,但并发弱
  3. 编译器优化:编译器的质量和优化能力可以大大影响生成代码的性能。

  4. 语言的抽象级别:高级语言通常比低级语言具有更好的生产力,但其性能可能较差,因为更高的抽象级别可能会引入额外的计算和内存开销

    • 补充一下,Rust 所宣传的 "零成本抽象"。指在不牺牲性能的情况下,使用更高级别的抽象(如函数,对象,泛型等)

      编译器生成的机器码与你直接用低级代码编写的效果是一样的。

      这种编译时优化和消除开销的能力,主要来自于Rust的先进的类型系统、借用检查器以及优秀的LLVM后端编译器。

  5. I/O操作:对于某些应用程序,如网络服务器、数据库等,I/O操作可能是性能瓶颈。语言对I/O操作的支持会影响性能

  6. 特定平台的优化:某些编程语言可能会针对特定的硬件或操作系统平台进行优化,从而在这些平台上表现出较好的性能。例如,Objective-C和Swift在iOS设备上的性能优于其他语言