diff --git a/modules/session/memory.go b/modules/session/memory.go
new file mode 100644
index 0000000000..9c493bfe15
--- /dev/null
+++ b/modules/session/memory.go
@@ -0,0 +1,216 @@
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+// Copyright 2019 The Gitea Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package session
+
+import (
+	"container/list"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/go-macaron/session"
+)
+
+// MemStore represents a in-memory session store implementation.
+type MemStore struct {
+	sid        string
+	lock       sync.RWMutex
+	data       map[interface{}]interface{}
+	lastAccess time.Time
+}
+
+// NewMemStore creates and returns a memory session store.
+func NewMemStore(sid string) *MemStore {
+	return &MemStore{
+		sid:        sid,
+		data:       make(map[interface{}]interface{}),
+		lastAccess: time.Now(),
+	}
+}
+
+// Set sets value to given key in session.
+func (s *MemStore) Set(key, val interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data[key] = val
+	return nil
+}
+
+// Get gets value by given key in session.
+func (s *MemStore) Get(key interface{}) interface{} {
+	s.lock.RLock()
+	defer s.lock.RUnlock()
+
+	return s.data[key]
+}
+
+// Delete deletes a key from session.
+func (s *MemStore) Delete(key interface{}) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	delete(s.data, key)
+	return nil
+}
+
+// ID returns current session ID.
+func (s *MemStore) ID() string {
+	return s.sid
+}
+
+// Release releases resource and save data to provider.
+func (*MemStore) Release() error {
+	return nil
+}
+
+// Flush deletes all session data.
+func (s *MemStore) Flush() error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.data = make(map[interface{}]interface{})
+	return nil
+}
+
+// MemProvider represents a in-memory session provider implementation.
+type MemProvider struct {
+	lock        sync.RWMutex
+	maxLifetime int64
+	data        map[string]*list.Element
+	// A priority list whose lastAccess newer gets higher priority.
+	list *list.List
+}
+
+// Init initializes memory session provider.
+func (p *MemProvider) Init(maxLifetime int64, _ string) error {
+	p.lock.Lock()
+	p.maxLifetime = maxLifetime
+	p.lock.Unlock()
+	return nil
+}
+
+// update expands time of session store by given ID.
+func (p *MemProvider) update(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if e, ok := p.data[sid]; ok {
+		e.Value.(*MemStore).lastAccess = time.Now()
+		p.list.MoveToFront(e)
+		return nil
+	}
+	return nil
+}
+
+// Read returns raw session store by session ID.
+func (p *MemProvider) Read(sid string) (_ session.RawStore, err error) {
+	p.lock.RLock()
+	e, ok := p.data[sid]
+	p.lock.RUnlock()
+
+	if ok {
+		if err = p.update(sid); err != nil {
+			return nil, err
+		}
+		return e.Value.(*MemStore), nil
+	}
+
+	// Create a new session.
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	s := NewMemStore(sid)
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Exist returns true if session with given ID exists.
+func (p *MemProvider) Exist(sid string) bool {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	_, ok := p.data[sid]
+	return ok
+}
+
+// Destory deletes a session by session ID.
+func (p *MemProvider) Destory(sid string) error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	e, ok := p.data[sid]
+	if !ok {
+		return nil
+	}
+
+	p.list.Remove(e)
+	delete(p.data, sid)
+	return nil
+}
+
+// Regenerate regenerates a session store from old session ID to new one.
+func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
+	if p.Exist(sid) {
+		return nil, fmt.Errorf("new sid '%s' already exists", sid)
+	}
+
+	s, err := p.Read(oldsid)
+	if err != nil {
+		return nil, err
+	}
+
+	if err = p.Destory(oldsid); err != nil {
+		return nil, err
+	}
+
+	s.(*MemStore).sid = sid
+
+	p.lock.Lock()
+	defer p.lock.Unlock()
+	p.data[sid] = p.list.PushBack(s)
+	return s, nil
+}
+
+// Count counts and returns number of sessions.
+func (p *MemProvider) Count() int {
+	return p.list.Len()
+}
+
+// GC calls GC to clean expired sessions.
+func (p *MemProvider) GC() {
+	p.lock.RLock()
+	for {
+		// No session in the list.
+		e := p.list.Back()
+		if e == nil {
+			break
+		}
+
+		if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
+			p.lock.RUnlock()
+			p.lock.Lock()
+			p.list.Remove(e)
+			delete(p.data, e.Value.(*MemStore).sid)
+			p.lock.Unlock()
+			p.lock.RLock()
+		} else {
+			break
+		}
+	}
+	p.lock.RUnlock()
+}
diff --git a/modules/session/virtual.go b/modules/session/virtual.go
index b79686b7b2..b90f03417a 100644
--- a/modules/session/virtual.go
+++ b/modules/session/virtual.go
@@ -5,6 +5,7 @@
 package session
 
 import (
+	"container/list"
 	"encoding/json"
 	"fmt"
 	"sync"
@@ -38,7 +39,7 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
 	// This is only slightly more wrong than modules/setting/session.go:23
 	switch opts.Provider {
 	case "memory":
-		o.provider = &session.MemProvider{}
+		o.provider = &MemProvider{list: list.New(), data: make(map[string]*list.Element)}
 	case "file":
 		o.provider = &session.FileProvider{}
 	case "redis":