# 1. Python属于动态强类型语言

  1. 动态还是静态指的是编译期还是运行期确定类型
  2. 强类型指的是不会发生隐式类型转换php js 都是弱类型语言)

# 2. 为什么要用Python?

  1. 胶水语言,轮子多,应用广泛
  2. 语言灵活,生产力高

缺点:

  1. 性能问题
  2. 代码维护问题(动态语言一时爽,代码重构火葬场)
  3. python2/3兼容问题

# 3. 什么是monkey patch?哪些地方用到类?自己如何实现?

  1. 所谓的monkey patch就是运行时替换
  2. 比如gevent库需要修改内置的socket
from gevent import monkey; 
monkey.patch_socket()

# 4. 什么是自省(Introspection)?

  1. 运行时判断一个对象的类型的能力
  2. python一切皆对象,用typeidinstance获取对象类型信息
  3. inspect模块提供了更多获取对象信息的函数
  4. id返回的是变量在内存中的地址
print(type([1, 2, 3]))
# <class 'list'>

print(isinstance([1], list))
# True

a = 1
print(id(a)) 
# 140723276551840

# 5. is和等号的区别

  1. is判断两个变量的地址是否相同
  2. 等号==判断两个变量值是否相同
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b) # True
print(a is b) # False

print(id(a)) # 2583559616320
print(id(b)) #  2583559616128

注意:

  • 判断一个变量是不是None的时候,用is None 不是==None因为None本身是单例的

# 6. Python 之禅

import this 查看python准则

# 7. Python2和3差别

  • Print 成为函数,python2 中是关键字
  • 编码问题,python3 不再有 unicode 对象,默认 str 就是 unicode
  • 除法变化,python3 除号返回浮点数,python2 是直接截断,为了得到和 python2 一样的效果,使用//双斜杠

Python2 需要s=u’中文’type(s) // Unicode Python3 直接可以 s=’中文’ type(s) // str

# 7.1. Python3 改进

  1. 类型注解(type hint)帮助 IDE 进行类型检查
  2. 优化的super()方便直接调用父类函数
  3. Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx
  4. 高级解包操作,a, b, *rest = range(10)
  5. 生成的pyc 文件统一放到__pycache__
  6. 一些内置库的修改 urllib selector
  7. 性能优化

# 7.2. Python3新增:

  1. yield from 链接子生成器
  2. Asyncio内置库,asyc/await原生协程支持异步编程
  3. 新的内置库 enum, mock, asyncio, ipaddress, concurrent.futures

# 7.3. 兼容2/3的工具

  1. Six 模块
  2. 2to3等工具转换代码
  3. __future__ 在 python2 实现 python3 功能

# 8. replace()re.sub() 字符串字符替换

# 8.1. replace()

testStr = 'aa:bb[cc'

testStr.replace(':', '_')
# 每次只能替换一个字符或字符串

# 8.2. re.sub()

import re
testStr = 'aa:bb[cc}'

# 把 :[} 替换成 _
re.sub(r'[:[}]', '_', testStr)

re.sub() 的第一个参数是pattern,使用正则表达式,所以例子中 r'[:[}]' 代表 [] 中的任何一个字符

# 9. Python如何传递参数?

  1. 传值还是传递引用? 都不是,python 唯一支持的参数传递是共享传参
  2. Call by Object (call by Object Reference or call by Sharing)
  3. Call by sharing(共享传参)。函数形参获得实参中各个引用的副本

一切皆对象,变量是对象的标识,比如a, b可以指向 [1,2]

例题:

def flist(l):
  l.append(0)
  print(l)

l = []
flist(l) # [0]
flist(l) # [0, 0]
def fstr(s):
  s += 'a'
  print(s)

s = 'hehe'
fstr(s) # hehea
fstr(s) # hehea

上述例子解释:

  1. Call by Object Reference 就是传递参数的时候,形参和实参都指向同一个对象,它既不是拷贝一个值,也不是直接操作内存
  2. 但结果有两种,对于可变对象可以直接修改,对于不可变对象很像copy了这个值

# 10. 哪些是不可变对象?哪些可变?

  1. 不可变对象 bool/int/float/tuple/str/frozenset
  2. 可变对象 list/set/dict

例题:

def clearList(l):
  l = []

ll = [1, 2, 3]

clearList(ll)

print(ll) # [1, 2, 3]

函数内的局部变量指向了新的对象,并不影响全局变量。这一点和 JS 不同。

# 11. *args, **kwargs含义是什么

  1. 用来处理可变参数
  2. *args 被打包成tuple
  3. **kwargs 被打包成dict
def multi(*args):
  print(type(args), args) 


multi(1,2,3) 
# <class 'tuple'> (1, 2, 3)
def multi(**kwargs):
  print(type(kwargs), kwargs)


multi(a=1, b=2) 
# <class 'dict'> {'a': 1, 'b': 2}

# 12. 如何自定义异常?

  1. 继承Exception实现自定义异常(为什么不是BaseException)(就不能用ctrl+c 结束程序了)
  2. 给异常加上个一些附加信息
  3. 处理一些业务相关的特定异常(raise MyException
class MyException(Exception):
  pass

try:
  raise MyException('my exception')
except MyException as e:
  print(e) # my exception

# 13. Python生成器与协程

什么是生成器?Generator

  1. 生成器就是可以生成值的函数
  2. 当一个函数里有了yield关键字就成了生成器
  3. 生成器可以挂起执行并且保持当前执行的状态

生成器例子:

def simple_gen():
  yield 'hello'
  yield 'world'

gen = simple_gen()
print(type(gen)) # <class 'generator'>
print(next(gen)) # hello
print(next(gen)) # world

基于生成器的协程(python2)

  • Python3之前没有原生协程,只有基于生成器的协程
  • pep342(coroutines via Enhanced Generators)增强生成器功能
  • 生成器可以通过yield暂停执行和产出数据
  • 同时支持send()向生成器发送数据和throw()向生成器抛异常

协程注意点:

  1. 协程需要使用send(None) 或者next(coroutine)来预激(prime)才能启动
  2. yield处协程会暂停执行
  3. 单独的yield value会产出值给调用方
  4. 可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield表达式左边的value=yield
  5. 协程执行完成后(没有遇到下一个yield语句)会抛出StopIteration异常

# 13.1. 协程

  • 协程(Coroutine)是一种轻量级的用户态线程,实现的是非抢占式的调度,即由当前协程切换到其他协程由当前协程来控制。
  • 目前的协程框架一般都是设计成1:N模式。所谓1:N就是一个线程作为一个容器里面放置多个协程。
  • 那么谁来适时的切换这些协程?答案是由协程自己主动让出CPU,也就是每个协程池里面有一个调度器,这个调度器是被动调度的。意思就是他不会主动调度。

# 14. Python 单元测试

Unit Testing

  • 针对程序模块进行正确性检验
  • 一个函数,一个类进行验证
  • 自底向上保证程序正确性

为什么要写单元测试? 三无代码不可取(无文档、无注释、无单测)

  • 保证代码逻辑的正确性(甚至有些采用测试驱动开发TDD)
  • 单测影响设计,易测的代码往往是高内聚低耦合的。
  • 回归测试,防止改一处导致整个服务不可用

单元测试相关的库:

  • nose/pytest 较为常用
  • mock模块用来模拟替换网络请求等
  • coverage统计测试覆盖率

# 15. Python 深拷贝和浅拷贝

  • 什么是深拷贝?什么是浅拷贝?
  • 如何实现深拷贝? copy.deepcopy(x)
  • Python如何正确初始化一个二维数组?

浅拷贝是对另外一个变量的内存地址的拷贝,这两个变量指向同一个内存地址的变量值

深拷贝:一个变量对另一个变量的值拷贝

# 16. Python 内置数据结构算法

用过哪些内置的算法数据结构?

  • sorted
  • dict/list/set/tuple

Python 内置数据结构算法

# 16.1. Python dict底层结构

  • Dict底层使用的是哈希表
  • 为了支持快速查找使用了哈希表作为底层结构
  • 哈希表平均查找时间复杂度O(1)
  • CPython解释器使用二次探查解决哈希冲突问题

哈希表示如何解决冲突的?链接法探查法 哈希表示如何扩容的?

# 16.2. Listtuple区别

  • 都是线性结构,支持下标访问
  • List是可变对象,tuple保存的引用不可变(不可变对象)
  • List不能作为字典的keytuple可以(可变对象不可hash

注意: 如果tuple本身保存的是可变对象,则可以修改这个可变对象

t = ([1], 2, 3)

t[0].append(1)

print(t) # ([1, 2], 2, 3)

# 17. 什么是LRUCache?

  • Least-Recently-Used替换掉最近最少使用的对象
  • 缓存剔除策略,当缓存空间不够用的时候需要一种方式剔除key
  • 常见的有LRULFU等(Ffrequency)
  • LRU通过使用一个循环双端队列不断把最新访问的key放到表头

什么是LRUCache

# 17.1. 如何实现LRUCache

  • 字典用来缓存,循环双端链表用来记录访问顺序
  • 利用python内置的dict + collection.OrderedDict实现
  • Dict用来当做k/v键值对的缓存
  • OrderedDict用来实现最新最近访问的key
from collections import OrderedDict

class LRUCache:
  def __init__(self, capacity = 128):
    self.Od = OrderedDict
    self.capacity = capacity

  def get(self, key): # 每次访问更新最近使用的key
    if key in self.Od:
      val = self.Od[key]
      self.Od.move_to_end(key)
      return val
    else:
      return -1

  def put(self, key, value): # 更新k/v
    if key in self.Od:
      del self.Od[key]
      self.Od[key] = value  # 更新key到表头
    else:
      self.Od[key] = value
      if len(self.Od) > self.capacity:
        self.Od.popitem(last = False)

# 18. 组合和继承

  • 优先使用组合(保持代码简单),而非继承
  • 组合是使用其他的类实例作为自己的一个属性(Has – a关系)
  • 子类继承父类的属性和方法(Is a 关系)

collections.Deque=>stack就是组合

# 19. Classmethod/staticmethod区别

  • 都可以通过Class.method()的方式使用
  • Classmethod的第一个参数是cls,可以引用类变量
  • Staticmethod使用起来和普通函数一样,只不过放在类里去组织
  • Classmethod是为了使用类变量
  • Staticmethod是代码组织的需要,完全可以放到类之外
class Person:
  country = 'China'

  def __init__(self, name):
    self.name = name

  @classmethod
  def print_c(cls):
    print(cls.country)

  @staticmethod
  def join_name(first, last):
    return first + last

# 20. 元类

  • 元类(Meta Class)是创建类的类
  • 元类允许我们控制类的生成,比如修改类的属性等
  • 使用type来定义元类
  • 元类最常见的一个使用场景就是ORM框架

# 20.1. type(‘child’, (Base, ), {})

三个参数,类的名称、基类(Base后必须加括号,否则就不是tuple了)、属性

class Base:
  pass

class Child(Base):
  pass

# 等价于如下格式:
SameChild = type('Child', (Base,), {})
# 带有方法的类
class ChildWithMethod(Base):
  bar = True
  
  def hello(self):
    print('hello')

def hello(self):
  print('hello')

# 等价定义
ChildWithMethod = type('ChildWithMethod', (Base,), {'bar': True, 'hello': hello})

# 21. __init____new__的区别?

  1. 用法不同

    • new用于创建实例,所以该方法是在实例创建之前被调用,是类级别的方法,是静态方法。
    • init用于初始化实例,在实例创建之后被调用,是实例级别的方法,用于设置对象属性的初始值。
  2. 传入参数不同

    • new至少有一个参数cls,代表当前类,此参数在实例化使由python解释器自动识别。
    • init至少有一个参数self,就是__new__返回的实例。
  3. 返回值不同

    • __new__必须有返回值,返回实例对象;
    • __init__不需要有返回值

子类没有重新定义new方法时,python默认调用父类的new方法类来构造该类实例,可以一直追溯到objectnew方法

# 21.1. __new__用途:

  1. 可以用于继承一些不可变的class,比如intstrtuple,提供一个自定义这些类的实例化过程的途径,一般通过重载new方法实现。
  2. 还可以用于实现单例模式。
class PositiveInteger(int):
  def __new__(cls, value):
    return super(PositiveInteger, cls).__new__(cls, abs(value))

a = PositiveInteger(-10)
print(a)

# 22. 装饰器 Decorator

  • 一切皆对象,函数也可以当做参数传递
  • 装饰器是接收函数作为参数,添加功能后返回一个新函数的函数(类)
  • Python中通过@使用装饰器

编写一个记录函数执行时间的装饰器

import time

def log_time(func):
  def _log(*args, **kwargs):
    beg = time.time()
    res = func(*args, **kwargs)
    print('use time: {}'.format(time.time() - beg))
    return res
  return _log

@log_time
def mysleep():
  time.sleep(1)

mysleep()

@只是语法糖 还可以这样:

newsleep = log_time(mysleep)
newsleep()

# 22.1. 如何使用类编写装饰器

import time

class LogTime:
  def __call__(self, func)
    def _log(*args, **kwargs):
      beg = time.time()
      res = func(*args, **kwargs)
      print('use time: {}'.format(time.time() - beg))
      return res
    return _log

@LogTime()
def mysleep():
  time.sleep(1)

mysleep()

# 23. python 实现单例模式

class Singleton:
  def __new__(cls, *args, **kwargs):
    if not hasattr(cls, '_instance'):
      _instance = super().__new__(cls, *args, **kwargs)
      cls._instance = _instance
    return cls._instance

class MyClass(Singleton):
  pass

c1 = MyClass()
c2 = MyClass()

assert c1 is c2

# 24. python 迭代器模式

  • Python内置对迭代器模式的支持
  • 比如用for遍历各种Iterable的数据类型
  • Python可以实现__next____iter__实现迭代器

栈的迭代:

from collections import deque

class Stack(object):

  def __init__(self):
    self._deque = deque()

  def push(self, val):
    return self._deque.append(val)

  def pop(self):
    return self._deque.pop()

  def empty(self):
    return len(self._deque) == 0

  def __iter__(self):
    # 使栈可以迭代,后进先出
    res = []
    for i in self._deque:
      res.append(i)
    for i in reversed(res):
      yield i

s = Stack()

s.push(1)
s.push(2)
for i in s:
  print(i)

IterableIterator的区别?

# 25. 函数式编程

Python支持部分函数式编程特性

  • 把电脑的运算视作视觉上的函数计算(lambda演算)
  • 高阶函数:map/reduce/filter
  • 无副作用,相同的函数调用始终会产生同样的结果

# 25.1. 函数式编程优点

  1. 代码简洁,开发快速(大量使用函数,减少了代码的重复)
  2. 接近自然语言,易于理解(抽象程度高)
  3. 更方便的代码管理 (利于单元测试以及模块化组合)
  4. 易于并发编程 (不修改变量,不存在锁线程问题,不需要考虑死锁)

# 25.2. 计算机(Computer)和计算(Compute)的区别:

  • 在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
  • 而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
  • 对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
  • 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
  • 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
  • Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

# 26. 什么是闭包(Closure)

  • 绑定了外部作用域的变量的函数(引用了外部自由变量的函数)
  • 特性:自由变量会和闭包函数同时存在
  • 即使程序离开外部作用域,如果仍然可见,绑定变量不会销毁
  • 每次运行外部函数都会重新创建闭包

# 27. IO多路复用

五种IO模型(Unix网络编程中提到了5中IO网络模型)

  • Blocking IO 阻塞式IO
  • Nonblocking IO 非阻塞式IO
  • IO multiplexing IO多路复用
  • Signal Driven IO 信号驱动IO
  • Asynchronous IO 异步IO

后两种不常用,一般使用IO多路复用比较多

# 27.1. 提升服务器的并发能力?

  • 多线程模型,创建新的线程处理请求
  • 多进程模型,创建新的进程处理请求
    • 线程/进程创建开销比较大,可以用线程池方式解决
    • 线程和进程比较占用资源,难以同时创建太多
  • IO多路复用,实现单进程同时处理多个socket请求

# 27.2. 什么是IO多路复用?

  • 操作系统提供的同时监听多个socket的机制
  • 为了实现高并发需要一种机制并发处理多个socket
  • Linux常见的是select/poll/epoll
  • 可以使用单线程单进程处理多个socket

# 27.3. 为什么select能实现高并发?

select可以同时处理多个socket,有一个就绪应用程序 代码就可以处理它

# 27.4. Python如何实现IO多路复用?

  • Python封装了操作系统的多路复用(select/poll/epoll)
  • Python2 select模块
  • Python3 selectors模块

# 28. Python并发网络库

你用过哪些并发网络库?

  • Tornado Gevent Asyncio
  • Tornado并发网络库,同时也是一个web微框架
  • Gevent绿色框架(greenlet)实现并发,猴子补丁修改内置socket(修改阻塞的内置库为非阻塞的)
  • Asyncio python3内置的并发网络库,基于原生协程

# 28.1. Tornado框架

  • Tornado适用于微服务,实现Restful接口
  • 底层基于Linux多路复用
  • 可以通过协程或者回调实现异步编程
  • 生态不完善,相应的异步框架比如ORM不完善

# 28.2. Gevent:高性能的并发网络库

  • 基于轻量级绿色线程(greenlet)实现并发
  • 需要注意monkey patchgevent修改了内置的socket为非阻塞
  • 配合gunicorngevent部署作为wsgi server

# 28.3. Asyncio 基于协程实现的内置的并发网络库

  • 协程+事件循环
  • 应用不够广泛,生态不够完善,aiohttp

并发网络库底层一般都是基于IO多路复用实现

# 29. WSGI和web框架

什么WSGI?

  • Python web server gateway interface(pep3333)
  • 解决python web server乱象 mod_python, CGI, FastCGI
  • 描述了web server(Gunicorn/uWSGI)如何与web框架(Flask/Djano)交互,web框架如何处理请求

# 29.1. 常用python web框架对比

  • Django:大而全,内置ORMAdmin等组件,第三方插件较多
  • Flask:微框架,插件机制,比较灵活
  • Tornado:异步支持的微框架和异步网络库

CPU时间和IO时间可以并行,所以并发尽管CPU时间和串行时间相同,但有IO的存在,使得可以缩短总时间。

# 30. python多线程编程

多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

多线程目的:

  • 更好的利用CPU资源,提高并发数量,避免阻塞(异步调用)

多线程缺点:

  • 如果有大量的线程,会影响性能,因为操作系统需要在它们之前切换
  • 更多的线程需要更多的内存空间
  • 需要防止线程死锁

当开启一个程序的时候,会默认开启一个主线程

  • join方法:在子线程完成后才执行主线程(阻塞主线程)
  • setDaemon方法:主线程结束后,会把子线程一起结束,不论它是否执行完。要在start方法之前调用
  • joinsetDaemon方法的作用相反
def test():
  start_time = time.time()
  t1 = Thread(target = sleep_task, args = (2,))
  t1.start()

  t2 = Thread(target = sleep_task, args = (3,))
  t2.start()

  t1.join() # 阻塞主线程
  t2.join()

  time.sleep(1)
  end_time = time.time()
  print(end_time - start_time)

实例化Thread两种方式:

  1. 创建任务函数,然后t1 = Thread(target=task, args=(2,)) (任务后面不能加括号,否则传递的是执行后的结果,即null;只有一个参数的话,一定要加逗号,否则不是tuple
  2. 创建类,继承Thread,然后t1 = MyThread(2)
def sleep_task(sleep_time):
  print('start {}'.format(sleep_time))
  time.sleep(sleep_time)
  print('end {}'.format(sleep_time))

def SleepThread(Thread):
  def __init__(self, sleep_time):
    self.sleep_time = sleep_time
    super().__init__() # 调用父类的初始化方法
  
  def run(self):
    print('start {}'.format(self.sleep_time))
    time.sleep(self.sleep_time)
    print('end {}'.format(self.sleep_time))

# 31. Python性能分析与优化,GIL

Global Interpreter LockCpython中的)

  1. Cpython解释器的内存管理并不是线程安全的
  2. 保护多线程情况下对python对象的访问
  3. Cpython使用简单的锁机制避免多个线程同时执行字节码

GIL的影响,限制了程序的多核执行:

  1. 同一个时间只能有一个线程执行字节码
  2. CPU密集程序难以利用多核优势
  3. IO期间会释放GIL,对IO密集程序影响不大

GIL

# 31.1. 如何规避GIL影响

区分 CPUIO 密集程序

  1. CPU 密集可以使用多进程+进程池
  2. IO 密集使用多线程/协程
  3. Cython 扩展

# 31.2. GIL的实现

每隔一段时间尝试释放当前线程的锁并让其他线程获取锁并执行

# 31.3. 为什么有了GIL还要关注线程安全?

Python中什么操作才是原子的?一步到位执行完

  1. 一个操作如果是一个字节码执行可以完成就是原子的
  2. 原子的是可以保证线程安全的
  3. 使用dis操作来分析字节码

# 31.4. 如何剖析程序性能?

使用Profile工具(内置或第三方)

  1. 内置的profile/cprofile等工具
  2. 使用pyflame(uber开源)的火焰图工具

# 32. 服务端性能优化措施

Web应用一般语言不会成为瓶颈

  1. 数据结构与算法优化
  2. 数据库层:索引优化,慢查询消除,批量操作减少IO,NoSQL
  3. 网络IO:批量操作,pipeline操作减少IO
  4. 缓存:使用内存数据库 redis/Memcached
  5. 异步: asyncio, celery
  6. 并发:gevent/多线程

# 33. GIL

Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全。每个CPU在同一个时间只能执行一个线程(在单核CPU下的多线程其实都只是并发,不是并行) 只有cpython解释器有GIL,所以说python慢不严谨,其他解释器如pypyjpython没有GIL

某个线程想要执行,必须先拿到GIL,我们可以把GIL看做是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行,GIL会释放的情况:

  1. 时间片释放-指定时间释放
  2. 遇到IO释放(所以python并不是串行)

Python和java对比: Python和java对比

  1. 同一时刻一个进程内只有一个能拿到GIL,所以多线程很多时候是单线程的
  2. 限制了线程并发的数量(同时拿到GIL锁和互斥锁的线程才可以执行)

串行、并行、并发: 串行、并行、并发

IO操作时间比CPU操作时间高几十万倍,所以GIL锁的存在不会使python比java执行效率低,原因:

  1. IO操作不会占用CPU时间
  2. CPU速度远远高于IO速度

不同任务的不同策略:

  1. 对应Web系统,高IO的程序,GIL可以忽略不计
  2. 计算任务可以使用多进程编程

既然GIL保证安全,但是GIL又有时间片的概念,是否矛盾?

  • GIL会保证字节码的安全

# 34. pandas保存文件的中文乱码

utf_8_sig代替utf-8

df.to_csv('tempscore.csv', encoding='utf_8_sig')

# 35. xpath 取值

  1. /li/a/@href 这样取的是href的内容
  2. /li/a/text() 这样取得是text内容

# 36. Python的垃圾回收机制原理

  • 引用计数为主(缺点:循环引用无法解决)
  • 引入标记清除分代回收解决引用计数的问题
  • 引用计数为主+标记清除和分代回收为辅

del 不是销毁对象,而是减少对象的引用计数(一切皆对象),只有引用计数为0时才会进行回收

# 36.1. 标记清除

从垃圾回收的根对象开始,不断的往下查找可达对象,可达对象就是有向图,线表示引用关系,把不可达的点置灰,认为没有对象引用它们,就可以把它们清除 (根对象一般就是栈里面的对象)

# 36.2. 分代回收

把对象的生命周期分三代0 1 2,每一代都使用双向链表标记这些对象,把创建的新的对象称为第0代,每隔一段时间,分代进行标记清除,第0代没有被清除会转移到更老的一代,比如第1