Python3 源碼閱讀 – 內存管理機制

Python 內存管理分層架構

/* An object allocator for Python.

   Here is an introduction to the layers of the Python memory architecture,
   showing where the object allocator is actually used (layer +2), It is
   called for every object allocation and deallocation (PyObject_New/Del),
   unless the object-specific allocators implement a proprietary allocation
   scheme (ex.: ints use a simple free list). This is also the place where
   the cyclic garbage collector operates selectively on container objects.


    Object-specific allocators
    _____   ______   ______       ________
   [ int ] [ dict ] [ list ] ... [ string ]       Python core         |
+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
    _______________________________       |                           |
   [   Python's object allocator   ]      |                           |
+2 | ####### Object memory ####### | <------ Internal buffers ------> |
    ______________________________________________________________    |
   [          Python's raw memory allocator (PyMem_ API)          ]   |
+1 | <----- Python memory (under PyMem manager's control) ------> |   |
    __________________________________________________________________
   [    Underlying general-purpose allocator (ex: C library malloc)   ]
 0 | <------ Virtual memory allocated for the python process -------> |

   =========================================================================
    _______________________________________________________________________
   [                OS-specific Virtual Memory Manager (VMM)               ]
-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |
    __________________________________   __________________________________
   [                                  ] [                                  ]
-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |

*/

reference:Objects/obmalloc.c

layer 3: Object-specific memory(int/dict/list/string....)
		python 實現並維護
		用戶對Python對象的直接操作,主要是各類特定對象的緩衝池機制,緩衝池,比如小整數對象池等等
layer 2: Python's object allocator
		實現了創建/銷毀python對象的接口(PyObject_New/Del),涉及對象參數/引用計數等

layer 1: Python's raw memory allocator (PyMem_ API)
		包裝了第0層的內存管理接口,提供同一個raw memory管理接口
		封裝的原因:不同操作系統C行為不一致,保證可移植性,相同語義相同行為
		
layer 0: Underlying general-purpose allocator (ex: C library malloc)
		操作系統提供的內存管理接口,由操作系統實現並管理,Python不能干涉這一層的行為,大內存 分配調用malloc函數分配內存

Python 內存分配策略之-block,pool

Python中有分為大內存和小內存,512K為分界線

  • 大內存使用系統malloc進行分配

  • 小內存使用python內存池進行分配

1. 如果要分配的內存空間大於 SMALL_REQUEST_THRESHOLD bytes(512 bytes), 將直接使用layer 1的內存分配接口進行分配
2. 否則, 使用不同的block來滿足分配需求
申請一塊大小28字節的內存, 實際從內存中劃到32字節的一個block (從size class index為3的pool裏面劃出)

block

內存塊block 是python內存的最小單位

* For small requests we have the following table:
 *
 * Request in bytes     Size of allocated block      Size class idx
 * ----------------------------------------------------------------
 *        1-8                     8                       0
 *        9-16                   16                       1
 *       17-24                   24                       2
 *       25-32                   32                       3
 *       33-40                   40                       4
 *       41-48                   48                       5
 *       49-56                   56                       6
 *       57-64                   64                       7
 *       65-72                   72                       8
 *        ...                   ...                     ...
 *      497-504                 504                      62
 *      505-512                 512                      63
 *
 *      0, SMALL_REQUEST_THRESHOLD + 1 and up: routed to the underlying
 *      allocator.
 */

pool

pool內存池,管理block, 一個pool管理着一堆固定大小的內存塊,在Python中, 一個pool的大小通常為一個系統內存頁. 4kB

#define SYSTEM_PAGE_SIZE        (4 * 1024)
#define SYSTEM_PAGE_SIZE_MASK   (SYSTEM_PAGE_SIZE - 1)

#define POOL_SIZE               SYSTEM_PAGE_SIZE        /* must be 2^N */
#define POOL_SIZE_MASK          SYSTEM_PAGE_SIZE_MASK

pool的4kB內存 = pool_header + block集合(N多大小一樣的block)

typedef uint8_t block;

/* Pool for small blocks. */
struct pool_header {
    union { block *_padding;
            uint count; } ref;          /* number of allocated blocks    */
    block *freeblock;                   /* pool's free list head         */
    struct pool_header *nextpool;       /* next pool of this size class  */
    struct pool_header *prevpool;       /* previous pool       ""        */
    uint arenaindex;                    /* index into arenas of base adr */
    uint szidx;                         /* block size class index        */
    uint nextoffset;                    /* bytes to virgin block         */
    uint maxnextoffset;                 /* largest valid nextoffset      */
};

pool_header 作用

與其他pool鏈接, 組成雙向鏈表
2. 維護pool中可用的block, 單鏈表
3. 保存 szidx , 這個和該pool中block的大小有關係, (block size=8, szidx=0), (block size=16, szidx=1)...用於內存分配時匹配到擁有對應大小block的pool

pool 初始化

void *
PyObject_Malloc(size_t nbytes)
{
  ...

          init_pool:
            // 1. 連接到 used_pools 雙向鏈表, 作為表頭
            // 注意, 這裏 usedpools[0] 保存着 block size = 8 的所有used_pools的表頭
            /* Frontlink to used pools. */
            next = usedpools[size + size]; /* == prev */
            pool->nextpool = next;
            pool->prevpool = next;
            next->nextpool = pool;
            next->prevpool = pool;
            pool->ref.count = 1;

            // 如果已經初始化過了...這裏看初始化, 跳過
            if (pool->szidx == size) {
                /* Luckily, this pool last contained blocks
                 * of the same size class, so its header
                 * and free list are already initialized.
                 */
                bp = pool->freeblock;
                pool->freeblock = *(block **)bp;
                UNLOCK();
                return (void *)bp;
            }


            /*
             * Initialize the pool header, set up the free list to
             * contain just the second block, and return the first
             * block.
             */
            // 開始初始化pool_header
            // 這裏 size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;  其實是Size class idx, 即szidx
            pool->szidx = size;

            // 計算獲得每個block的size
            size = INDEX2SIZE(size);

            // 注意 #define POOL_OVERHEAD           ROUNDUP(sizeof(struct pool_header))
            // bp => 初始化為pool + pool_header size,  跳過pool_header的內存
            bp = (block *)pool + POOL_OVERHEAD;

            // 計算偏移量, 這裏的偏移量是絕對值
            // #define POOL_SIZE               SYSTEM_PAGE_SIZE        /* must be 2^N */
            // POOL_SIZE = 4kb, POOL_OVERHEAD = pool_header size
            // 下一個偏移位置: pool_header size + 2 * size
            pool->nextoffset = POOL_OVERHEAD + (size << 1);
            // 4kb - size
            pool->maxnextoffset = POOL_SIZE - size;

            // freeblock指向 bp + size = pool_header size + size
            pool->freeblock = bp + size;

            // 賦值NULL
            *(block **)(pool->freeblock) = NULL;
            UNLOCK();
            return (void *)bp;
        }

pool 進行block分配 – 總體代碼

  if (pool != pool->nextpool) {   //
            /*
             * There is a used pool for this size class.
             * Pick up the head block of its free list.
             */
            ++pool->ref.count;
            bp = pool->freeblock; // 指針指向空閑block起始位置
            assert(bp != NULL);

            // 代碼-1
            // 調整 pool->freeblock (假設A節點)指向鏈表下一個, 即bp首字節指向的下一個節點(假設B節點) , 如果此時!= NULL
            // 表示 A節點可用, 直接返回
            if ((pool->freeblock = *(block **)bp) != NULL) {
                UNLOCK();
                return (void *)bp;
            }

            // 代碼-2
            /*
             * Reached the end of the free list, try to extend it.
             */
            // 有足夠的空間, 分配一個, pool->freeblock 指向後移
            if (pool->nextoffset <= pool->maxnextoffset) {
                /* There is room for another block. */
                // 變更位置信息
                pool->freeblock = (block*)pool +
                                  pool->nextoffset;
                pool->nextoffset += INDEX2SIZE(size);


                *(block **)(pool->freeblock) = NULL; // 注意, 指向NULL
                UNLOCK();

                // 返回bp
                return (void *)bp;
            }

            // 代碼-3
            /* Pool is full, unlink from used pools. */  // 滿了, 需要從下一個pool獲取
            next = pool->nextpool;
            pool = pool->prevpool;
            next->prevpool = pool;
            pool->nextpool = next;
            UNLOCK();
            return (void *)bp;
        }

pool進行block分配 -1

內存塊尚未分配完, 且此時不存在回收的block, 全新進來的時候, 分配第一塊block

(pool->freeblock = *(block **)bp) == NULL

當進入代碼邏輯2時,表示有空閑的block, 代碼2的執行流程圖如下

pool進行block分配 – 2 回收了某幾個block

回收涉及的代碼:

void
PyObject_Free(void *p)
{
    poolp pool;
    block *lastfree;
    poolp next, prev;
    uint size;

    pool = POOL_ADDR(p);
    if (Py_ADDRESS_IN_RANGE(p, pool)) {
        /* We allocated this address. */
        LOCK();
        /* Link p to the start of the pool's freeblock list.  Since
         * the pool had at least the p block outstanding, the pool
         * wasn't empty (so it's already in a usedpools[] list, or
         * was full and is in no list -- it's not in the freeblocks
         * list in any case).
         */
        assert(pool->ref.count > 0);            /* else it was empty */
        // p被釋放, p的第一個字節值被設置為當前freeblock的值
        *(block **)p = lastfree = pool->freeblock;
        // freeblock被更新為指向p的首地址
        pool->freeblock = (block *)p;

        // 相當於往list中頭插入了一個節點

     ...
    }
}

每釋放一個block,該blcok就會變成pool->freeblock的頭結點, 假設已經連續分配了5塊, 第1塊和第4塊被釋放,此時的內存圖示如下:

此時再一個block分配調用進來, 執行分配, 進入的邏輯是代碼-1

bp = pool->freeblock; // 指針指向空閑block起始位置
// 代碼-1
// 調整 pool->freeblock (假設A節點)指向鏈表下一個, 即bp首字節指向的下一個節點(假設B節點) , 如果此時!= NULL
// 表示 A節點可用, 直接返回
if ((pool->freeblock = *(block **)bp) != NULL) {
    UNLOCK();
    return (void *)bp;
}

pool進行block分配 – 3 pool用完了

pool中內存空間都用完了, 進入代碼-3

/* Pool is full, unlink from used pools. */  // 滿了, 需要從下一個pool獲取
next = pool->nextpool;
pool = pool->prevpool;
next->prevpool = pool;
pool->nextpool = next;
UNLOCK();
return (void *)bp;

Python 內存分配策略之-arena

arena: 多個pool聚合的結果, 可放置64個pool

#define ARENA_SIZE              (256 << 10)     /* 256KB */

arena結構

一個完整的arena = arena_object + pool集合

/* Record keeping for arenas. */
struct arena_object {
    /* The address of the arena, as returned by malloc.  Note that 0
     * will never be returned by a successful malloc, and is used
     * here to mark an arena_object that doesn't correspond to an
     * allocated arena.
     */
    uintptr_t address;

    /* Pool-aligned pointer to the next pool to be carved off. */
    block* pool_address;

    /* The number of available pools in the arena:  free pools + never-
     * allocated pools.
     */
    uint nfreepools;

    /* The total number of pools in the arena, whether or not available. */
    uint ntotalpools;

    /* Singly-linked list of available pools. */
    struct pool_header* freepools;

    /* Whenever this arena_object is not associated with an allocated
     * arena, the nextarena member is used to link all unassociated
     * arena_objects in the singly-linked `unused_arena_objects` list.
     * The prevarena member is unused in this case.
     *
     * When this arena_object is associated with an allocated arena
     * with at least one available pool, both members are used in the
     * doubly-linked `usable_arenas` list, which is maintained in
     * increasing order of `nfreepools` values.
     *
     * Else this arena_object is associated with an allocated arena
     * all of whose pools are in use.  `nextarena` and `prevarena`
     * are both meaningless in this case.
     */
    struct arena_object* nextarena;
    struct arena_object* prevarena;
};
arena_object的作用
1. 與其他arena連接, 組成雙向鏈表
2. 維護arena中可用的pool, 單鏈表
  • pool_header和管理的blocks內存是一塊連續的內存 => pool_header被申請時,其管理的的block集合的內存一併被申請 uint maxnextoffset; /* largest valid nextoffset */
  • arena_object 和其管理的內存是分離的 => arena_object被申請時,其管理的pool集合的內存沒有被申請,而是在某一時刻建立關係的

arena的兩種狀態

/* The head of the singly-linked, NULL-terminated list of available
 * arena_objects.
 */
// 單鏈表
static struct arena_object* unused_arena_objects = NULL;

/* The head of the doubly-linked, NULL-terminated at each end, list of
 * arena_objects associated with arenas that have pools available.
 */
// 雙向鏈表
static struct arena_object* usable_arenas = NULL;

arena 初始化

* Allocate a new arena.  If we run out of memory, return NULL.  Else
 * allocate a new arena, and return the address of an arena_object
 * describing the new arena.  It's expected that the caller will set
 * `usable_arenas` to the return value.
 */
static struct arena_object*
new_arena(void)
{
    struct arena_object* arenaobj;
    uint excess;        /* number of bytes above pool alignment */
    void *address;
    static int debug_stats = -1;

    if (debug_stats == -1) {
        const char *opt = Py_GETENV("PYTHONMALLOCSTATS");
        debug_stats = (opt != NULL && *opt != '\0');
    }
    if (debug_stats)
        _PyObject_DebugMallocStats(stderr);

    // 判斷是否需要擴充"未使用"的arena_object列表
    if (unused_arena_objects == NULL) {
        uint i;
        uint numarenas;
        size_t nbytes;

        /* Double the number of arena objects on each allocation.
         * Note that it's possible for `numarenas` to overflow.
         */
        // 確定需要申請的個數, 首次初始化, 16, 之後每次翻倍
        numarenas = maxarenas ? maxarenas << 1 : INITIAL_ARENA_OBJECTS;
        if (numarenas <= maxarenas)
            return NULL;                /* overflow */
#if SIZEOF_SIZE_T <= SIZEOF_INT
        if (numarenas > SIZE_MAX / sizeof(*arenas))
            return NULL;                /* overflow */
#endif
        nbytes = numarenas * sizeof(*arenas);
        // 申請內存
        arenaobj = (struct arena_object *)PyMem_RawRealloc(arenas, nbytes);
        if (arenaobj == NULL)
            return NULL;
        arenas = arenaobj;

        /* We might need to fix pointers that were copied.  However,
         * new_arena only gets called when all the pages in the
         * previous arenas are full.  Thus, there are *no* pointers
         * into the old array. Thus, we don't have to worry about
         * invalid pointers.  Just to be sure, some asserts:
         */
        assert(usable_arenas == NULL);
        assert(unused_arena_objects == NULL);

        /* Put the new arenas on the unused_arena_objects list. */
        for (i = maxarenas; i < numarenas; ++i) {
            arenas[i].address = 0;              /* mark as unassociated */
            // 新申請的一律為0, 標識着這個arena處於"未使用"
            arenas[i].nextarena = i < numarenas - 1 ?
                                   &arenas[i+1] : NULL;
        }

         // 將其放入unused_arena_objects鏈表中
        // unused_arena_objects 為新分配內存空間的開頭
        /* Update globals. */
        unused_arena_objects = &arenas[maxarenas];
        maxarenas = numarenas;
    }

    /* Take the next available arena object off the head of the list. */
    assert(unused_arena_objects != NULL);
    // 從unused_arena_objects中, 獲取一個未使用的object
    arenaobj = unused_arena_objects;
    unused_arena_objects = arenaobj->nextarena;  // 更新鏈表
    assert(arenaobj->address == 0);
    // 申請內存, 256KB, 內存地址賦值給arena的address. 這塊內存可用
    address = _PyObject_Arena.alloc(_PyObject_Arena.ctx, ARENA_SIZE);
    if (address == NULL) {
        /* The allocation failed: return NULL after putting the
         * arenaobj back.
         */
        arenaobj->nextarena = unused_arena_objects;
        unused_arena_objects = arenaobj;
        return NULL;
    }
    arenaobj->address = (uintptr_t)address;

    ++narenas_currently_allocated;
    ++ntimes_arena_allocated;
    if (narenas_currently_allocated > narenas_highwater)
        narenas_highwater = narenas_currently_allocated;
    arenaobj->freepools = NULL;
    /* pool_address <- first pool-aligned address in the arena
       nfreepools <- number of whole pools that fit after alignment */
    arenaobj->pool_address = (block*)arenaobj->address;
    arenaobj->nfreepools = MAX_POOLS_IN_ARENA;
    // 將pool的起始地址調整為系統頁的邊界
    // 申請到 256KB, 放棄了一些內存, 而將可使用的內存邊界pool_address調整到了與系統頁對齊
    excess = (uint)(arenaobj->address & POOL_SIZE_MASK);
    if (excess != 0) {
        --arenaobj->nfreepools;
        arenaobj->pool_address += POOL_SIZE - excess;
    }
    arenaobj->ntotalpools = arenaobj->nfreepools;

    return arenaobj;
}

從arenas取一個arena進行初始化

arena分配

new一個全新的arena

static void*
pymalloc_alloc(void *ctx, size_t nbytes)
 {
            // 剛開始沒有可用的arena
            if (usable_arenas == NULL) {
              // new一個, 作為雙向鏈表的表頭
              usable_arenas = new_arena();
              if (usable_arenas == NULL) {
                  UNLOCK();
                  goto redirect;
              }

              usable_arenas->nextarena =
                  usable_arenas->prevarena = NULL;

           }

          .......

          // 從arena中獲取一個pool
          pool = (poolp)usable_arenas->pool_address;
          assert((block*)pool <= (block*)usable_arenas->address +
                                 ARENA_SIZE - POOL_SIZE);
          pool->arenaindex = usable_arenas - arenas;
          assert(&arenas[pool->arenaindex] == usable_arenas);
          pool->szidx = DUMMY_SIZE_IDX;

          // 更新 pool_address 向下一個節點
          usable_arenas->pool_address += POOL_SIZE;
          // 可用節點數量-1
          --usable_arenas->nfreepools;

}

從全新的arena中獲取一個pool

假設arena是舊的, 怎麼分配的pool, 跟pool分配block原理一樣,使用單鏈表記錄freepools

pool = usable_arenas->freepools;
if (pool != NULL) {

當arena中一整塊pool被釋放的時候

/* Free a memory block allocated by pymalloc_alloc().
   Return 1 if it was freed.
   Return 0 if the block was not allocated by pymalloc_alloc(). */
static int
pymalloc_free(void *ctx, void *p) {
    struct arena_object* ao;
    uint nf;  /* ao->nfreepools */

    /* Link the pool to freepools.  This is a singly-linked
               * list, and pool->prevpool isn't used there.
              */
    ao = &arenas[pool->arenaindex];
    pool->nextpool = ao->freepools;
    ao->freepools = pool;
    nf = ++ao->nfreepools;
}

在pool整塊被釋放的時候, 會將pool加入到arena->freepools作為單鏈表的表頭, 然後, 在從非全新arena中分配pool時, 優先從arena->freepools裏面取, 如果取不到, 再從arena內存塊裏面獲取

注: 上圖中nfreepools = n – 2

當arena1用完了,獲取arena1指向的下一個節點arena2

static void*
pymalloc_alloc(void *ctx, size_t nbytes)
{


          // 當發現用完了最後一個pool!!!!!!!!!!!
          // nfreepools = 0
          if (usable_arenas->nfreepools == 0) {
              assert(usable_arenas->nextarena == NULL ||
                     usable_arenas->nextarena->prevarena ==
                     usable_arenas);
              /* Unlink the arena:  it is completely allocated. */

              // 找到下一個節點!
              usable_arenas = usable_arenas->nextarena;
              // 右下一個
              if (usable_arenas != NULL) {
                  usable_arenas->prevarena = NULL; // 更新下一個節點的prevarens
                  assert(usable_arenas->address != 0);
              }
              // 沒有下一個, 此時 usable_arenas = NULL, 下次進行內存分配的時候, 就會從arenas數組中取一個

          }

  }

注意: 這裡有個邏輯, 就是每分配一個pool, 就檢查是不是用到了最後一個, 如果是, 需要變更usable_arenas到下一個可用的節點, 如果沒有可用的, 那麼下次進行內存分配的時候, 會判定從arenas數組中取一個

arena回收

內存分配和回收最小單位是block, 當一個block被回收的時候, 可能觸發pool被回收, pool被回收, 將會觸發arena的回收機制

    1. arena中所有pool都是閑置的(empty), 將arena內存釋放, 返回給操作系統
    1. 如果arena中之前所有的pool都是佔用的(used), 現在釋放了一個pool(empty), 需要將 arena加入到usable_arenas, 會加入鏈表表頭
    1. 如果arena中empty的pool個數n, 則從useable_arenas開始尋找可以插入的位置. 將arena插入. (useable_arenas是一個有序鏈表, 按empty pool的個數, 保證empty pool數量越多, 被使用的幾率越小, 最終被整體釋放的機會越大)

內存分配的步驟

關注點:如何尋找到一塊可用的nbytes的blcok內存?

pool = usedpools[size + size]

if pool:

​ pool 沒滿,取一個blcok返回

​ pool 滿了,從下一個pool取一個blcok返回

else:

​ 獲取arena, 从里面初始化一個pool, 拿到第一個blcok返回

進行內存分配和銷毀, 所有操作都是在pool上進行的

問題: pool中所有block的size一樣, 但是在arena中, 每個pool的size都可能不一樣, 那麼最終這些pool是怎麼維護的? 怎麼根據大小找到需要的block所在的pool? => usedpools

pool在內存池中的三種狀態

  1. used狀態:pool中至少有一個block已經被使用,並且至少有一個block未被使用,這種狀態的pool受控於Python內部維護的usedpool數組
  2. full狀態:pool中所有的block都已經被使用,這種狀態的pool在arena中, 但不在arena的freepools鏈表中,處於full的pool各自獨立, 不會被鏈表維護起來
  3. empty狀態:pool中所有的blcok都未被使用,處於這個狀態的pool的集合通過其pool_header中的nextpool構成一個鏈表,鏈表的表頭示arena_object中的freepools

Python內部維護的usedpools數組是一個非常巧妙的實現,維護着所有的處於used狀態的pool,當申請內存時,python就會通過usedpools尋找到一個可用的pool(處於used狀態),從中分配一個block。因此我們想,一定有一個usedpools相關聯的機制,完成從申請的內存的大小到size class index之間的轉換,否則python就無法找到最合適的pool了。這種機制和usedpools的結構有着密切的關係,我們看一下它的結構

usedpools

usedpools數組: 維護着所有處於used狀態的pool, 當申請內存的時候, 會通過usedpools尋找到一塊可用的(處於used狀態的)pool, 從中分配一個block。

//obmalloc.c
typedef uint8_t block;
#define PTA(x)  ((poolp )((uint8_t *)&(usedpools[2*(x)]) - 2*sizeof(block *)))
#define PT(x)   PTA(x), PTA(x)

//在我當前的機器就是512/8=64個,對應的size class index就是從0到63
#define NB_SMALL_SIZE_CLASSES   (SMALL_REQUEST_THRESHOLD / ALIGNMENT)

static poolp usedpools[2 * ((NB_SMALL_SIZE_CLASSES + 7) / 8) * 8] = {
    PT(0), PT(1), PT(2), PT(3), PT(4), PT(5), PT(6), PT(7)
#if NB_SMALL_SIZE_CLASSES > 8
    , PT(8), PT(9), PT(10), PT(11), PT(12), PT(13), PT(14), PT(15)
#if NB_SMALL_SIZE_CLASSES > 16
    , PT(16), PT(17), PT(18), PT(19), PT(20), PT(21), PT(22), PT(23)
#if NB_SMALL_SIZE_CLASSES > 24
    , PT(24), PT(25), PT(26), PT(27), PT(28), PT(29), PT(30), PT(31)
#if NB_SMALL_SIZE_CLASSES > 32
    , PT(32), PT(33), PT(34), PT(35), PT(36), PT(37), PT(38), PT(39)
#if NB_SMALL_SIZE_CLASSES > 40
    , PT(40), PT(41), PT(42), PT(43), PT(44), PT(45), PT(46), PT(47)
#if NB_SMALL_SIZE_CLASSES > 48
    , PT(48), PT(49), PT(50), PT(51), PT(52), PT(53), PT(54), PT(55)
#if NB_SMALL_SIZE_CLASSES > 56
    , PT(56), PT(57), PT(58), PT(59), PT(60), PT(61), PT(62), PT(63)
#if NB_SMALL_SIZE_CLASSES > 64
#error "NB_SMALL_SIZE_CLASSES should be less than 64"
#endif /* NB_SMALL_SIZE_CLASSES > 64 */
#endif /* NB_SMALL_SIZE_CLASSES > 56 */
#endif /* NB_SMALL_SIZE_CLASSES > 48 */
#endif /* NB_SMALL_SIZE_CLASSES > 40 */
#endif /* NB_SMALL_SIZE_CLASSES > 32 */
#endif /* NB_SMALL_SIZE_CLASSES > 24 */
#endif /* NB_SMALL_SIZE_CLASSES > 16 */
#endif /* NB_SMALL_SIZE_CLASSES >  8 */
};

如果正在申請28字節, python首先會獲取(size class index) size = (uint )(nbytes - 1) >> ALIGNMENT_SHIFT 顯然這裏size=3, 那麼在usedpools中,尋找第3+3=6個元素,發現usedpools[6]的值是指向usedpools[4]的地址

//obmalloc.c
/* Pool for small blocks. */
struct pool_header {
    union { block *_padding;
            uint count; } ref;          /* 當然pool裏面的block數量    */
    block *freeblock;                   /* 一個鏈表,指向下一個可用的block   */
    struct pool_header *nextpool;       /* 指向下一個pool  */
    struct pool_header *prevpool;       /* 指向上一個pool       ""        */
    uint arenaindex;                    /* 在area裏面的索引 */
    uint szidx;                         /* block的大小(固定值?後面說)     */
    uint nextoffset;                    /* 下一個可用block的內存偏移量         */
    uint maxnextoffset;                 /* 最後一個block距離開始位置的距離     */
};

顯然是從usedpools[6](即usedpools+4)開始向後偏移8個字節(一個ref的大小加上一個freeblock的大小)后的內存,正好是usedpools[6]的地址(即usedpools+6),這是python內部的trick

當我們要申請一個size class為32字節的pool,想要將其放入這個usedpools中時,要怎麼做呢?從上面的描述我們知道,只需要進行usedpools[i+i] -> nextpool = pool即可,其中i為size class index,對應於32字節,這個i為3.當下次需要訪問size class 為32字節(size class index為3)的pool時,只需要簡單地訪問usedpools[3+3]就可以得到了。python正是使用這個usedpools快速地從眾多的pool中快速地尋找到一個最適合當前內存需求的pool,從中分配一塊block。

//obmalloc.c
static int
pymalloc_alloc(void *ctx, void **ptr_p, size_t nbytes)
{
    block *bp;
    poolp pool;
    poolp next;
    uint size;
    ...
    LOCK();
    //獲得size class index
    size = (uint)(nbytes - 1) >> ALIGNMENT_SHIFT;
    //直接通過usedpools[size+size],這裏的size不就是我們上面說的i嗎?
    pool = usedpools[size + size];
    //如果usedpools中有可用的pool
    if (pool != pool->nextpool) {
        ... //有可用pool
    }
    ... //無可用pool,嘗試獲取empty狀態的pool
}  

內存池全局結構

參考:

pyhton源碼閱讀-內存管理機制

python源碼解析第17章-python內存管理與垃圾回收

後期查缺補漏需要看的文章

Memory management by Zpoint
Memory management in Python

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

動物園因疫情撐不下去……就怕全餓死 浣熊恐率先安樂死

摘錄自2020年04月25日自由時報報導

英國一間小型動物園因為無法負擔龐大的支出,負責人說,最壞的打算就是將動物「安樂死」,而浣熊將會首當其衝。

綜合外媒報導,51歲的安迪.科威爾(Andy Cowell)在英國肯特郡聖瑪利胡(St Mary Hoo, Kent)經營一間小型動物園,受疫情影響,園區遊客銳減,他花盡畢生積蓄,還是無法負擔一日2000英鎊(約新台幣7萬4000元)的龐大開銷,最壞的打算是將動物安樂死,避免牠們活活餓死。由於浣熊被英國政府列為入侵物種,因此會最先成為安樂死對象。

生活環境
國際新聞
英國
武漢肺炎
動物園
疫情下的食衣住行

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

荷蘭養殖場水貂感染病毒 當局稱傳人可能性極小

摘錄自2020年04月26日自由時報報導

荷蘭農業部今(26日)宣布,境內有水貂感染武漢肺炎病毒,目前發現病例的兩座水貂養殖場已被隔離,此為該國首次傳出動物感染。

綜合外媒報導,荷蘭衛生部表示,由於養殖場內有水貂出現呼吸困難的跡象,檢測後確定染上病毒,據信是經由身上帶有病毒的員工傳染,不過,病毒在養殖場進一步傳播給其他人或動物的可能性極小。

衛生部表示,目前相關人員正在進行研究,呼籲人們不要經過養殖場的400公尺範圍內。

生活環境
國際新聞
荷蘭
武漢肺炎
養殖場

動物與大環境變遷

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

火雞群感染高病原性H7N3 美國農業部:沒有傳染人類案例

摘錄自2020年4月26日ETtoday報導

繼2017年後出現H7N3首例!美國農業部(United States Department of Agriculture,USDA)在9日證實,在南卡羅萊納州(Carolina)的(Chesterfield)市,發現有火雞感染H7N3高病原性(HPAI)禽流感病毒,目前該區域已經下令封鎖,並隔離相關禽類,以確保不進入人類或動物的口中。

根據美國農業部公告,H7N3目前「沒有任何人類感染案例」,因此沒有立即性的健康疑慮,但是為了以防萬一,建議在烹調家禽與雞蛋時應以165˚F(約74℃)溫度殺死病毒與細菌。

根據世界動物衛生組織(OIE)規範,如果「4-8週齡的雞感染後死亡率達75%」即「高病原性禽流感」,通常出現在H5、H7型上。人類如果感染禽流感,可能出現高燒、呼吸急促等症狀,由甲型禽流感(如H5N1、H5N6、H7N9和H10N8病毒)引起的症狀比一般流感嚴重,大多數患者須住院治療。

生活環境
永續發展
土地利用
國際新聞
美國
火雞
禽流感
公共衛生
經濟動物
動物福利
糧食

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

南極地區首次發現青蛙化石碎片

摘錄自2020年4月27日俄羅斯衛星通訊社報導

根據發表在《Scientific Reports》期刊的研究報告,科研人員在南極半島北端的西莫爾島發現了數塊青蛙的頭骨和部分髖骨化石碎片,這種古老的生物是南美地區現代頭盔蛙科的近親。

這一的發現讓科學家對南極大陸的古代氣候有了新的認識。這些化石碎片距今約4000萬年,頭骨形狀可以看出這隻青蛙屬於頭盔蛙科。頭盔蛙科現生種生活在南美安第斯山脈中部的溫暖潮濕山谷中。這表明,至少4000萬年前,南極洲地區也是類似的氣候。

這一發現改變了科學家對南極大陸氣候變化的認識。大多數科學家認為,大約4000萬年前,南極洲與澳洲大陸分離後迅速被冰層覆蓋。但是一些證據表明,在南極大陸與南半球其他現代大陸完全分離前,南極洲冰蓋就已經開始形成。

生態保育
物種保育
生物多樣性
國際新聞
南極
古生物學
化石
青蛙
兩棲類
南極

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

捂緊錢包!12月開始這些又高又大的SUV準備來圈錢了!

長寬高達到4949/1930/1785mm,軸距也達到2810mm,數據不亞於同級別競爭對手。新車提供5座和7座版本車型,其中7座採用2+3+2的座椅布局。而在長安CS95眾多的產品賣點的重中之重,都放在這台2。0TGDI發動機,最大馬力為233ps,最大扭矩360Nm,官方百公里加速為9。

隨着家庭人口的增多,以及消費層次的升級,7座SUV逐漸成為SUV市場中不可忽視的一股消費主力。特別是傳祺GS8的上市,不只是成功聚焦了眾人的眼光,更是讓消費者重新定義了屬於7座SUV的魅力。於是,在利益和現實因素的推動下,越來越多的車企瞄準了7座SUV市場紛紛出大招。

相比傳統的5座SUV,其實市面上7座SUV的產品線非常單薄,以致於寡頭垄斷的局面曾經持續了很長一段時間。事實上,隨着7座SUV這一細分市場的升溫,除了考慮漢蘭達、銳界、傳祺GS8,你還可以考慮這些即將上市的7座SUV。

金杯蒂阿茲

預售價:8.68萬元

上市時間:2016年12月

大部分對於華晨金杯的印象,還停留在爛大街的商務車上。而近期亮相的金杯蒂阿茲,可謂完全“刷新”了國人對於金杯的認識。一眼望去,如果不仔細加以辨認,還以為是套上金杯logo的謳歌MDX。當然,外觀方面是懂的人自然懂。更何況,不到9萬的預售價已經相當有吸引力了。

內飾方面中規中矩,但好在配置應該比較豐富。新車搭載一台1.5T發動機,最大馬力154ps,峰值扭矩 210Nm。先期預計將推出一款手動擋5座車型,而準備購買自動擋以及7座版車型的消費者,或許還要等到明年才有機會下手。

長安CS95

預售價:未知

上市時間:2017年第一季度

繼哈弗、傳祺推出了7座SUV之後,長安CS95量產版也終於按耐不住選在這個節骨眼亮相。有意思的是,之前飽受吐槽的“回”字前臉,總算是被重新修飾了一番,整體效果看起來不錯。車身形象高大威猛,好一副硬派SUV的作風。這裏也不得不提到長安CS95的戰略意義,目前是被長安用來衝擊高端的首款中型SUV,產品力可想而知。

即然是一台全尺寸旗艦SUV,長安CS95的車身尺寸沒有讓人失望。長寬高達到4949/1930/1785mm,軸距也達到2810mm,數據不亞於同級別競爭對手。新車提供5座和7座版本車型,其中7座採用2+3+2的座椅布局。而在長安CS95眾多的產品賣點的重中之重,都放在這台2.0TGDI發動機,最大馬力為233ps,最大扭矩360Nm,官方百公里加速為9.8秒。

起亞KX7

預售價:未知

上市時間:2017年3月

迫不及待加入7座SUV戰場的,還有來自韓國的起亞。據悉,全新起亞KX7是基於上一代索蘭托平台開發,簡單理解而言又是一台中國特供車。新車繼續沿用家族式設計語言,造型穩重大氣,又不失時尚感。就外觀來講,起亞KX7顯然是非常符合國人的審美觀念的。

但看看起亞KX7整體的車身尺寸,一比較2790mm的漢蘭達和2850mm的銳界,軸距僅為2700mm的起亞KX7的產品短板就非常明顯了,尤其是第三排座椅舒適度會是一個很大障礙。不過,從起亞KX7所公布的配置情況,再結合韓系車一向的低價傳統,上市后的起亞KX7預計仍將通過高性價比的策略,力爭7座SUV市場份額。

大眾Teramont

預售價:未知

上市時間:2017年上半年

國產大眾途觀明年上市的消息,就已經讓多少大眾粉朝思暮想。與此同時,另一款大眾重磅車型的亮相再次引爆人們的話題點,那就是全新中大型SUV-大眾 Teramont。新車延續了大眾CrossBlue概念車的設計,形象霸氣魁梧,視覺上極具震撼力。

更牛逼的是,車身尺寸已經大大超過途銳,幾乎與奧迪Q7旗鼓相當,長寬高為5039/1989/1773mm,軸距也順利達到逆天的2980mm。就180cm大個子的體驗,無論是第二排還是第三排,空間可以用大到沒朋友形容。動力總成也非常給力,大眾Teramont採用2.0T高低功率版以及2.5T V6發動機,匹配7速濕式雙離合變速箱。如果不出意外,配合一個合理厚道的價位,以及大眾的神車光環,大眾Teramont大賣不成問題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

年輕人首選的SUV?這次給你們推薦點不一樣的

而本次所介紹的東南DX3則是東南品牌旗下的“小鮮肉”車型,外觀造型與DX7師出同源,由於車身更加短小緊湊,所以可以看出東南DX3的外觀更加具有時尚感和運動感,作為一款消費人群定義在三十歲以內年輕人的車型,東南DX3的顏值可以獲得一個比較高的分數。

年輕人首選的自主品牌SUV

現在年輕人購車已經不是什麼稀奇的事情,但是似乎業內出現了一種怪圈,就是說到年輕人首選的SUV車型,合資的似乎就只有通用集團的昂科拉、創酷;而自主品牌就只有吉利在今年新推出的跨界SUV帝豪GS,其實不然,在自主品牌中,還是有不少年輕人可以選擇的SUV。

為什麼要推薦自主品牌?

當手機逐漸智能化並且豐富了人們的生活之時,國產品牌的手機成為了市場的新寵,功能強大,價格實惠的國產品牌手機使用基數往往比國際知名品牌手機要來得多;反觀汽車也是如此,消費者更注重的是一台車的售價和配置,而逐步成長起來的自主品牌汽車未嘗不能成為預算通常不太多的年輕人首選的車型。

如果當一輛SUV品牌實力強勁,售價相對實惠,外觀還富有個性的話,作為購置第一台車以便於初入職場實現人生目標的年輕消費者來說,自然是一種不錯的選擇。

BJ20

指導價格:9.68-13.98萬

北京汽車是自主汽車較早出名的品牌之一,在上世紀八十年代左右的北京,如果誰可以開着一台“北京吉普”在大街上“招搖過市”,那絕對是拉風至極的一件事情。

而在今年中旬,北京汽車專門為年輕一代的消費者推出了一款城市SUV——BJ20,整車在外觀設計上繼承了北京汽車品牌家族式的經典元素,並且根據時下流行的審美趨勢進行融合,使得BJ20的外觀極具個性與硬派氣息。

內飾設計同樣看得出北京汽車的品牌形象已經逐漸成熟,家族化風格十分濃郁,平直簡練的風格配合上獨特的雙幅式方向盤造型,穩重中還透露出一絲個性與不羈。

BJ20搭載的是一台1.5T渦輪增壓發動機,最大馬力150匹,峰值扭矩210牛米,與之配合的是傳統的手動變速箱和一台CVT無級變速箱;儘管不帶四驅,但是超越同級別的底盤高度,在應對稍微惡劣的非鋪裝路面時還是能給予人不少的信心。

東南DX3

指導價格:6.79-10.09萬

東南也算是國內做SUV車型比較有歷史的汽車廠商了,之前與三菱的深度合作使得東南在造車方面有了一定的技術深度儲備,而且近年來與賓法合作,在外觀設計上也逐漸有了自己的風格和語言,東南DX7則算是一台掙回了眼球的SUV。

而本次所介紹的東南DX3則是東南品牌旗下的“小鮮肉”車型,外觀造型與DX7師出同源,由於車身更加短小緊湊,所以可以看出東南DX3的外觀更加具有時尚感和運動感,作為一款消費人群定義在三十歲以內年輕人的車型,東南DX3的顏值可以獲得一個比較高的分數。

內飾設計同樣讓人覺得印象深刻,方形幾何配合圓形出風口的造型挺富有視覺衝擊力,只是有一點小編不太能理解的是,為何多媒體中控的功能性操控按鍵會放置在副駕駛的一側?這在國內的在售量產車型上見得確實不多。

畢竟是與三菱有過深度合作的歷史,東南DX3採用的是源自三菱的兩款發動機,型號同為4A91,1.5T發動機最大馬力156匹,峰值扭矩220牛米,1.5L發動機最大馬力120匹,峰值扭矩143牛米,傳動系統是一款可模擬八個檔位,來自比利時邦奇的CVT變速箱,邦奇也是目前國內採用CVT變速箱的車型主要供應商之一。

寶駿510

指導價格:暫無(猜測6-8萬)

寶駿汽車相繼使用了730和560兩款車型打開了銷量之後,最近開始將目標消費群體轉向年輕人群,先是推出了一款售價實惠的兩廂轎車寶駿310,而今年廣州車展上發布的一款年輕定位的SUV也有可能成為未來的主力,那便是寶駿510,。

外觀層面寶駿510非常聰明的使用了當下流行的設計元素,前大燈分體式設計很容易使人聯想到自由光,多邊形的進氣格柵和懸浮式車頂的設計也是觀眾們非常熟悉的設計語言,但是這些融合在寶駿510的身上顯得挺和諧。

內飾層面採用的雙色拼接方式同樣顯得年輕運動,組合型的幾何板塊配合上啞光的裝飾,整個車廂氛圍營造得恰到好處。獨立式的多媒體显示屏幕也算是內飾當中比較出彩的亮點。

動力層面寶駿510搭載的是與寶駿730相同的1.5L自然吸氣發動機,最大馬力112匹,峰值扭矩147牛米,目前僅有6速手動變速箱,未來是否會換上與730相同的AMT變速箱暫時不得而知。

寶駿510的售價暫時沒有公布,但是從小型SUV的定位上看,會比6.98-9.48萬元定價的寶駿560要稍微低一些,小編大膽猜測頂配價格應該是在8萬元左右。究竟最終售價如何,還是比較值得期待。

全文總結:現在年輕一代的汽車消費群體逐漸在全國各地湧現,不僅僅是經濟較為發達的一線城市,在很多二三線甚至更低定位的城市當中,汽車也已經成為了人們日常生活的重要工具,擁有一輛自己的車也是很多年輕人普遍擁有的想法,而面對着現在不斷成熟完善的自主品牌,購買一台自主品牌車型作為人生首輛車,小編認為也沒什麼不好。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

10月銷量達7千多輛!8.98萬起這款日產技術SUV值得買嗎?

38萬車主點評:最滿意空間,乘坐空間很夠用。還有一次有個朋友喝多了,像爛泥一樣一動不動,我們直接把他抬到後備廂里拉回去。動力方面,捨得給油還是有推背感,不過開空調載着4個人,就顯得力不從心啦。目前行駛里程:我目前開了3980公里,平均百公里油耗只有6。

東風日產-啟辰T70

指導價:8.98-12.78萬

車主:神車奧拓

購買車型:2016款 2.0L CVT睿行版

裸車價格:9.78萬

車主點評:我最滿意它的底盤紮實,懸挂行程長,所以通過性好,另外它對於震動的過濾不錯。而內飾的硬塑料有點多!所以檔次感就不夠了。

目前行駛里程:我的T70買了才兩個月,跑了有1345公里,平均百公里油耗只有8.2L,我覺得CVT變速箱還是比較省油!

車主:笑出12塊腹肌

購買車型:2016款 1.6L 手動睿行版

裸車價格:8.38萬

車主點評:最滿意空間,乘坐空間很夠用。還有一次有個朋友喝多了,像爛泥一樣一動不動,我們直接把他抬到後備廂里拉回去!動力方面,捨得給油還是有推背感,不過開空調載着4個人,就顯得力不從心啦!

目前行駛里程:我目前開了3980公里,平均百公里油耗只有6.9L,手動擋相當省油。

車主:段迎風

購買車型:2015款 2.0L CVT睿趣版

裸車價格:11.50萬

車主點評:開起來其實類似日產逍客的感覺,底盤比較紮實。空間的表現也讓我給它豎起大拇指!動力感覺有點肉,這是日產CVT變速箱的特性。另外方向盤有些重,開起來比起本田繽智還是顯得笨重一些。

目前行駛里程:我的車是2015年12月購買的,到現在行駛了9500公里,平均百公里油耗9L,這樣的油耗還可以接受。

編者點評:

啟辰T70的價格不高,但是在底盤表現、質量、空間方面表現都不錯,只是配置會稍低一些。綜合性價比是不錯的!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

OAuth + Security – 5 – Token存儲升級(數據庫、Redis)

PS:此文章為系列文章,建議從第一篇開始閱讀。

在我們之前的文章中,我們當時獲取到Token令牌時,此時的令牌時存儲在內存中的,這樣顯然不利於我們程序的擴展,所以為了解決這個問題,官方給我們還提供了其它的方式來存儲令牌,存儲到數據庫或者Redis中,下面我們就來看一看怎麼實現。

不使用Jwt令牌的實現

  • 存儲到數據庫中(JdbcTokenStore)

使用數據庫存儲方式之前,我們需要先準備好對應的表。Spring Security OAuth倉庫可以找到相應的腳本:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql。該腳本為HSQL,所以需要根據具體使用的數據庫進行相應的修改,以MySQL為例,並且當前項目中,只需要使用到oauth_access_token和oauth_refresh_token數據表,所以將創建這兩個庫表的語句改為MySQL語句:

CREATE TABLE oauth_access_token (
	token_id VARCHAR ( 256 ),
	token BLOB,
	authentication_id VARCHAR ( 256 ),
	user_name VARCHAR ( 256 ),
	client_id VARCHAR ( 256 ),
	authentication BLOB,
	refresh_token VARCHAR ( 256 ) 
);

CREATE TABLE oauth_refresh_token ( 
token_id VARCHAR ( 256 ), 
token BLOB, authentication BLOB 
);

然後我們需要去配置對應的認證服務器,主要就是修改之前文章中TokenConfigure類中的tokenStore()方法:

// 同時需要注入數據源
@Autowired
private DataSource dataSource;
    
@Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

同時需要添加jdbc的依賴:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

認證服務器中的配置保持本系列第一章的配置不變,此處不再贅述。

其中若有不理解的地方,請參考該系列的第一篇文章

關於數據源的補充:

在我們項目中配置的數據源,可能不一定是使用的官方提供的格式,比如我們自定義的格式,或者使用第三方的數據源,那麼我們如何去配置呢?這裏以mybatis-plus的多數據源為例:

@Configuration
@EnableAuthorizationServer
public class FebsAuthorizationServerConfigure extends AuthorizationServerConfigurerAdapter {

    ......
    
    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;

    @Bean
    public TokenStore tokenStore() {
        DataSource dimplesCloudBase = dynamicRoutingDataSource.getDataSource("dimples_cloud_base");
        return new JdbcTokenStore(febsCloudBase);
    }
    ......
}

然後啟動項目,重新獲取Token進行測試,能正確的獲取到Token:

我們查看數據中,看是否已經存入相關信息到數據庫中:

  • 存儲到redis(RedisTokenStore)

令牌存儲到redis中相比於存儲到數據庫中來說,存儲redis中,首先在性能上有一定的提升,其次,令牌都有有效時間,超過這個時間,令牌將不可再用,而redis的可以給對應的key設置過期時間,完美切合需求,所有令牌存儲到redis中也是一種值得使用的方法。

首先,我們需要在項目中添加redis依賴,同時配置redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

新建配置類RedisConfigure

@Configuration
public class RedisConfigure{

    @Bean
    @ConditionalOnClass(RedisOperations.class)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用 String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的 key也採用 String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用 jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的 value序列化方式採用 jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
    
}

配置redis的連接

spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        min-idle: 8
        max-idle: 500
        max-active: 2000
        max-wait: 10000
    timeout: 5000

這時我們啟動項目測試,會發現項目將會報錯:

Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig

這是由於我們配置RedisConfigure時,使用到了一個ObjectMapper類,這個類需要我們引入Apache的一個工具包

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

最後,我們需要在認證服務器中配置token的存儲方式,還是同jdbc的配置,在tokenStore()方法中配置,打開我們的TokenConfigure類,然後做如下配置:

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean
public TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
}

認證服務器中的配置還是跟之前的一樣,保持不變,啟動項目,獲取Token測試:

我們查看Redis中,看是否已經存入相關信息到數據庫中:

  • 其它

我們打開TokenStore接口的實現類,會發現,還有一種JwkTokenStore,這種實際上就是將我們UUID格式令牌變成可以帶特殊含義的jwt格式的令牌,我們已經在第三篇文章中介紹過了,可以參考前面的文章。

但是我們需要明白一點的是,這種令牌還是存儲在內存中的,後期我們如何將其存儲到redis中是我們研究的方向。

在上面的token存儲到數據庫和存儲到redis中,我們會發現一個問題,那就是我們多次獲取令牌,但是其值是固定不變的,為了解決這個問題,我們可以使用如下方式解決:

@Bean
public TokenStore tokenStore() {
    RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
    // 解決每次生成的 token都一樣的問題
    redisTokenStore.setAuthenticationKeyGenerator(oAuth2Authentication -> UUID.randomUUID().toString());
    return redisTokenStore;
}

使用JWT令牌的實現

在之前的所有使用方法中,我們要麼是將Token存儲在數據庫或者redis中的時候,令牌的格式是UUID的無意義字符串,或者是使用JWT的有意義字符串,但是確是將令牌存儲在內存中,那麼,我們現在想使用JWT令牌的同時,也想將令牌存儲到數據庫或者Redis中。我們該怎麼配置呢?下面我們以Redis存儲為例,進行探索:

首先,我們還是要使用到TokenConfigure中的JWT和Redis配置:

@Autowired
private RedisConnectionFactory redisConnectionFactory;

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    //對稱秘鑰,資源服務器使用該秘鑰來驗證
    converter.setSigningKey(SIGNING_KEY);
    return converter;
}

@Bean
public TokenStore tokenStore() {
    return new RedisTokenStore(redisConnectionFactory);
}

接下來,是配置認證服務器,在認證服務器中,跟之前的配置有一點區別,如下:

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Autowired
private TokenStore tokenStore;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints
            // 配置密碼模式管理器
            .authenticationManager(authenticationManager)
            // 配置授權碼模式管理器
            .authorizationCodeServices(authorizationCodeServices())
            // 令牌管理
//                .tokenServices(tokenServices());
            .accessTokenConverter(jwtAccessTokenConverter)
            .tokenStore(tokenStore);

}

啟動項目,進行測試,獲取token

然後使用該令牌請求資源

至此,我們差不多完成了Token令牌的存儲和獲取

源碼傳送門: https://gitee.com/dimples820/dimples-explore

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

「封城新時代」垃圾量爆增! 防疫優先環保擺一邊

摘錄自2020年05月08日TVBS新聞網全球報導

全球進入「封城新時代」,人類減少外出,大大改善了地球環境,不過塑膠污染卻愈來愈嚴重。美國各州,為了防止重複使用的購物袋傳播病毒,開放使用塑膠袋,許多環保措施因此停擺。而宅經濟正夯,網購的貨品包裝,也製造出大量垃圾。

加州塑膠袋全面啟用,當地原先規定購買塑膠袋必須支付10美分,大約台幣3元錢,如今禁令暫停60天,因為衛生安全問題,暫時大於環保問題。就連舊金山,身為全美最先禁用塑膠袋的城市之一,也已經宣布,禁止消費者攜帶自己的環保杯、環保袋等用品到店內。

無論是外出購物,還是網購,似乎怎麼做都會製造塑膠廢棄物,知名零垃圾專家就教大家運用「5R方針」,包括Refuse拒絕垃圾、Reduse減量、Reuse重複使用、Recycle回收,以及Rot把廚餘做成堆肥。零廢棄專家BeaJohnson:「這5R在世界各地都適用,不管你身處什麼情況,包括全球大流行疫情期間也是。」

公害污染
污染治理
國際新聞
美國
塑膠袋
塑膠袋禁用政策
一次性包裝
一次性塑膠袋
廢棄物

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!