python - 为什么 python 的 "gc.collect()"没有按预期工作?

这是我的测试代码:

#! /usr/bin/python3
import gc
import ctypes

name = "a" * 50
name_id = id(name)
del name
gc.collect()
print(ctypes.cast(name_id, ctypes.py_object).value)

输出:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

在我看来,gc.collect() 应该清除变量 name 及其值,
但为什么我可以在 gc.collect() 之后通过 name_id 获取值?

最佳答案

您不应该期望 gc.collect() 在这里做任何事情gc 只是控制循环垃圾收集器,这是一个辅助垃圾收集器,因为 CPython 使用引用计数作为其主要内存管理策略。循环垃圾收集器处理引用循环,这里没有引用循环,所以 gc.collect 不会做任何事情。

In my opinion, gc.collect() should clean the variable name and it's value,

这根本不是 Python 的工作方式。 变量 不再与del name 一起存在,但对象 继续存在,在这种情况下,由于编译器优化。 Python 变量不像 C 变量,它们不是内存块,它们是引用特定命名空间中对象的名称

无论如何,反汇编代码会让您在这里有所了解:

In [1]: import dis

In [2]: dis.dis("""
   ...: import gc
   ...: import ctypes
   ...:
   ...: name = "a" * 50
   ...: name_id = id(name)
   ...: del name
   ...: gc.collect()
   ...: print(ctypes.cast(name_id, ctypes.py_object).value)
   ...: """)
  2           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (gc)
              6 STORE_NAME               0 (gc)

  3           8 LOAD_CONST               0 (0)
             10 LOAD_CONST               1 (None)
             12 IMPORT_NAME              1 (ctypes)
             14 STORE_NAME               1 (ctypes)

  5          16 LOAD_CONST               2 ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
             18 STORE_NAME               2 (name)

  6          20 LOAD_NAME                3 (id)
             22 LOAD_NAME                2 (name)
             24 CALL_FUNCTION            1
             26 STORE_NAME               4 (name_id)

  7          28 DELETE_NAME              2 (name)

  8          30 LOAD_NAME                0 (gc)
             32 LOAD_METHOD              5 (collect)
             34 CALL_METHOD              0
             36 POP_TOP

  9          38 LOAD_NAME                6 (print)
             40 LOAD_NAME                1 (ctypes)
             42 LOAD_METHOD              7 (cast)
             44 LOAD_NAME                4 (name_id)
             46 LOAD_NAME                1 (ctypes)
             48 LOAD_ATTR                8 (py_object)
             50 CALL_METHOD              2
             52 LOAD_ATTR                9 (value)
             54 CALL_FUNCTION            1
             56 POP_TOP
             58 LOAD_CONST               1 (None)
             60 RETURN_VALUE

因此,当您的代码块被编译 时,CPython 编译器注意到"a"*50 可以变成常量,所以它做到了。它存储代码对象的常量,直到该代码对象不再存在(在这种情况下,当解释器存在时)。由于此代码对象将维护对此字符串对象的引用,因此它将一直存在。

所以,更明确地说:

In [4]: code = compile("""name = "a" * 50""", filename='foo', mode='exec')

In [5]: code
Out[5]: <code object <module> at 0x7ff7c12495d0, file "foo", line 1>

In [6]: code.co_consts
Out[6]: ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', None)

另请注意,Python 内存管理非常复杂且相当不透明。所有对象都在私有(private)管理的堆上处理。仅仅因为一个对象被“释放”并不意味着运行时不会简单地根据需要为相同类型(或其他合适类型)的对象重新使用那部分内存。看看这个:

In [1]: class Foo: pass

In [2]: import ctypes

In [3]: foo = Foo()

In [4]: id(foo)
Out[4]: 140559250737552

In [5]: del foo

In [6]: foo2 = Foo()

In [7]: id(foo2)
Out[7]: 140559250737680

In [8]: ctypes.cast(140559250737552, ctypes.py_object).value
Out[8]: <prompt_toolkit.lexers.pygments.RegexSync at 0x7fd68035c990>

In [9]: id(foo2)
Out[9]: 140559250737680

In [10]: del foo2

In [11]: ctypes.cast(140559250737680, ctypes.py_object).value
Out[11]: <prompt_toolkit.lexers.pygments.PygmentsLexer at 0x7fd68035ca10>

请注意在这些情况下您如何能够恢复一些对象,因为 ipython 交互式 shell 一直在创建对象,并且内部堆很乐意重新使用该内存。

看看在更简单的 REPL 中会发生什么:

(base) juanarrivillaga@50-254-139-253-static% python
Python 3.7.9 (default, Aug 31 2020, 07:22:35)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import ctypes
>>> class Foo: pass
...
>>> foo = Foo()
>>> i = id(foo)
>>> del foo
>>> ctypes.cast(i, ctypes.py_object).value
zsh: segmentation fault  python

是的。更预料的是,我们试图访问一部分内存,这部分内存不仅被内部堆回收,而且被 Python 进程释放,因此,我们遇到了段错误。 p>

关于python - 为什么 python 的 "gc.collect()"没有按预期工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64363718/

相关文章:

react-native - React native 每次打开页面时如何执行函数

python-3.x - Python Pandas : groupby one column, 只

vue.js - 如何制作可点击的带有悬停效果的q卡?

haskell - 在 Haskell 的 do 上下文中应用构造函数

python - Windows 上的 Gcloud 命令(使用 git bash)正在记录错误 :

reactjs - 不能在 JSX 属性中使用 bool 值

c++ - 仅当使用 unordered_map 而不是 vector 时,将 const 作为此参

java - 如何使用 void set 方法在静态常量帮助程序类中创建对象?

python - 在Python中随机生成二维列表

javascript - 在 React Native 中安排本地通知不起作用