diff --git a/src/core/core.cpp b/src/core/core.cpp
index 07da4c493..2293669e5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -171,6 +171,14 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
     return cpu_cores[core_index]->Scheduler();
 }
 
+Kernel::KernelCore& System::Kernel() {
+    return kernel;
+}
+
+const Kernel::KernelCore& System::Kernel() const {
+    return kernel;
+}
+
 ARM_Interface& System::ArmInterface(size_t core_index) {
     ASSERT(core_index < NUM_CPU_CORES);
     return cpu_cores[core_index]->ArmInterface();
@@ -185,12 +193,13 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     LOG_DEBUG(HW_Memory, "initialized OK");
 
     CoreTiming::Init();
+    kernel.Initialize();
 
     // Create a default fs if one doesn't already exist.
     if (virtual_filesystem == nullptr)
         virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
 
-    current_process = Kernel::Process::Create("main");
+    current_process = Kernel::Process::Create(kernel, "main");
 
     cpu_barrier = std::make_shared<CpuBarrier>();
     cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
@@ -201,7 +210,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     telemetry_session = std::make_unique<Core::TelemetrySession>();
     service_manager = std::make_shared<Service::SM::ServiceManager>();
 
-    Kernel::Init();
     Service::Init(service_manager, virtual_filesystem);
     GDBStub::Init();
 
@@ -246,7 +254,6 @@ void System::Shutdown() {
     renderer.reset();
     GDBStub::Shutdown();
     Service::Shutdown();
-    Kernel::Shutdown();
     service_manager.reset();
     telemetry_session.reset();
     gpu_core.reset();
@@ -265,7 +272,8 @@ void System::Shutdown() {
     }
     cpu_barrier.reset();
 
-    // Close core timing
+    // Shutdown kernel and core timing
+    kernel.Shutdown();
     CoreTiming::Shutdown();
 
     // Close app loader
diff --git a/src/core/core.h b/src/core/core.h
index 321104585..2c18f7193 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -12,6 +12,7 @@
 #include "common/common_types.h"
 #include "core/arm/exclusive_monitor.h"
 #include "core/core_cpu.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/scheduler.h"
 #include "core/loader/loader.h"
@@ -188,6 +189,12 @@ public:
         return current_process;
     }
 
+    /// Provides a reference to the kernel instance.
+    Kernel::KernelCore& Kernel();
+
+    /// Provides a constant reference to the kernel instance.
+    const Kernel::KernelCore& Kernel() const;
+
     /// Gets the name of the current game
     Loader::ResultStatus GetGameName(std::string& out) const {
         if (app_loader == nullptr)
@@ -246,6 +253,7 @@ private:
      */
     ResultStatus Init(Frontend::EmuWindow& emu_window);
 
+    Kernel::KernelCore kernel;
     /// RealVfsFilesystem instance
     FileSys::VirtualFilesystem virtual_filesystem;
     /// AppLoader used to load the current executing application
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index d3a734831..0f3ffdb60 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -12,6 +12,7 @@
 #include <utility>
 #include "common/assert.h"
 #include "common/common_types.h"
+#include "core/core.h"
 #include "core/hle/ipc.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/client_session.h"
@@ -135,7 +136,9 @@ public:
         if (context->Session()->IsDomain()) {
             context->AddDomainObject(std::move(iface));
         } else {
-            auto sessions = Kernel::ServerSession::CreateSessionPair(iface->GetServiceName());
+            auto& kernel = Core::System::GetInstance().Kernel();
+            auto sessions =
+                Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
             auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
             auto client = std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
             iface->ClientConnected(server);
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 134e41ebc..873d6c516 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -14,7 +14,7 @@
 
 namespace Kernel {
 
-ClientPort::ClientPort() = default;
+ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
 ClientPort::~ClientPort() = default;
 
 ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
@@ -27,7 +27,7 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
     active_sessions++;
 
     // Create a new session pair, let the created sessions inherit the parent port's HLE handler.
-    auto sessions = ServerSession::CreateSessionPair(server_port->GetName(), this);
+    auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
 
     if (server_port->hle_handler)
         server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index b1269ea5c..f3dfebbb1 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -11,8 +11,9 @@
 
 namespace Kernel {
 
-class ServerPort;
 class ClientSession;
+class KernelCore;
+class ServerPort;
 
 class ClientPort final : public Object {
 public:
@@ -44,7 +45,7 @@ public:
     void ConnectionClosed();
 
 private:
-    ClientPort();
+    explicit ClientPort(KernelCore& kernel);
     ~ClientPort() override;
 
     SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port.
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index fdffc648d..c114eaf99 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -11,7 +11,7 @@
 
 namespace Kernel {
 
-ClientSession::ClientSession() = default;
+ClientSession::ClientSession(KernelCore& kernel) : Object{kernel} {}
 ClientSession::~ClientSession() {
     // This destructor will be called automatically when the last ClientSession handle is closed by
     // the emulated application.
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index dabd93ed7..439fbdb35 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -12,8 +12,9 @@
 
 namespace Kernel {
 
-class ServerSession;
+class KernelCore;
 class Session;
+class ServerSession;
 class Thread;
 
 class ClientSession final : public Object {
@@ -41,7 +42,7 @@ public:
     std::shared_ptr<Session> parent;
 
 private:
-    ClientSession();
+    explicit ClientSession(KernelCore& kernel);
     ~ClientSession() override;
 };
 
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index 5623c4b6a..8967e602e 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -10,11 +10,11 @@
 
 namespace Kernel {
 
-Event::Event() {}
-Event::~Event() {}
+Event::Event(KernelCore& kernel) : WaitObject{kernel} {}
+Event::~Event() = default;
 
-SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) {
-    SharedPtr<Event> evt(new Event);
+SharedPtr<Event> Event::Create(KernelCore& kernel, ResetType reset_type, std::string name) {
+    SharedPtr<Event> evt(new Event(kernel));
 
     evt->signaled = false;
     evt->reset_type = reset_type;
diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h
index 3c20c05e8..27d6126b0 100644
--- a/src/core/hle/kernel/event.h
+++ b/src/core/hle/kernel/event.h
@@ -10,14 +10,18 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 class Event final : public WaitObject {
 public:
     /**
      * Creates an event
+     * @param kernel The kernel instance to create this event under.
      * @param reset_type ResetType describing how to create event
      * @param name Optional name of event
      */
-    static SharedPtr<Event> Create(ResetType reset_type, std::string name = "Unknown");
+    static SharedPtr<Event> Create(KernelCore& kernel, ResetType reset_type,
+                                   std::string name = "Unknown");
 
     std::string GetTypeName() const override {
         return "Event";
@@ -44,7 +48,7 @@ public:
     void Clear();
 
 private:
-    Event();
+    explicit Event(KernelCore& kernel);
     ~Event() override;
 
     ResetType reset_type; ///< Current ResetType
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index 6d9f7a02b..3a079b9a9 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -13,8 +13,6 @@
 
 namespace Kernel {
 
-HandleTable g_handle_table;
-
 HandleTable::HandleTable() {
     next_generation = 1;
     Clear();
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index aee3583e8..cac928adb 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -121,6 +121,4 @@ private:
     u16 next_free_slot;
 };
 
-extern HandleTable g_handle_table;
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 82a3fb5a8..db7aef766 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -13,6 +13,7 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/handle_table.h"
@@ -51,7 +52,9 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
 
     if (!event) {
         // Create event if not provided
-        event = Kernel::Event::Create(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
+        auto& kernel = Core::System::GetInstance().Kernel();
+        event =
+            Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
     }
 
     event->Clear();
@@ -90,12 +93,14 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
             rp.Skip(2, false);
         }
         if (incoming) {
+            auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+
             // Populate the object lists with the data in the IPC request.
             for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
-                copy_objects.push_back(Kernel::g_handle_table.GetGeneric(rp.Pop<Handle>()));
+                copy_objects.push_back(handle_table.GetGeneric(rp.Pop<Handle>()));
             }
             for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) {
-                move_objects.push_back(Kernel::g_handle_table.GetGeneric(rp.Pop<Handle>()));
+                move_objects.push_back(handle_table.GetGeneric(rp.Pop<Handle>()));
             }
         } else {
             // For responses we just ignore the handles, they're empty and will be populated when
@@ -230,17 +235,19 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
         ASSERT(copy_objects.size() == handle_descriptor_header->num_handles_to_copy);
         ASSERT(move_objects.size() == handle_descriptor_header->num_handles_to_move);
 
+        auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+
         // We don't make a distinction between copy and move handles when translating since HLE
         // services don't deal with handles directly. However, the guest applications might check
         // for specific values in each of these descriptors.
         for (auto& object : copy_objects) {
             ASSERT(object != nullptr);
-            dst_cmdbuf[current_offset++] = Kernel::g_handle_table.Create(object).Unwrap();
+            dst_cmdbuf[current_offset++] = handle_table.Create(object).Unwrap();
         }
 
         for (auto& object : move_objects) {
             ASSERT(object != nullptr);
-            dst_cmdbuf[current_offset++] = Kernel::g_handle_table.Create(object).Unwrap();
+            dst_cmdbuf[current_offset++] = handle_table.Create(object).Unwrap();
         }
     }
 
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 8c19e86d3..615d7901a 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -2,38 +2,291 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <array>
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <utility>
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+#include "core/core.h"
+#include "core/core_timing.h"
 #include "core/hle/kernel/handle_table.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/timer.h"
+#include "core/hle/lock.h"
+#include "core/hle/result.h"
 
 namespace Kernel {
 
-std::atomic<u32> Object::next_object_id{0};
+/**
+ * Callback that will wake up the thread it was scheduled for
+ * @param thread_handle The handle of the thread that's been awoken
+ * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
+ */
+static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_late) {
+    const auto proper_handle = static_cast<Handle>(thread_handle);
+    auto& system = Core::System::GetInstance();
 
-/// Initialize the kernel
-void Init() {
-    Kernel::ResourceLimitsInit();
-    Kernel::ThreadingInit();
-    Kernel::TimersInit();
+    // Lock the global kernel mutex when we enter the kernel HLE.
+    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
 
-    Object::next_object_id = 0;
-    // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
-    // reserved for low-level services
-    Process::next_process_id = 10;
+    SharedPtr<Thread> thread =
+        system.Kernel().RetrieveThreadFromWakeupCallbackHandleTable(proper_handle);
+    if (thread == nullptr) {
+        LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
+        return;
+    }
+
+    bool resume = true;
+
+    if (thread->status == ThreadStatus::WaitSynchAny ||
+        thread->status == ThreadStatus::WaitSynchAll ||
+        thread->status == ThreadStatus::WaitHLEEvent) {
+        // Remove the thread from each of its waiting objects' waitlists
+        for (auto& object : thread->wait_objects) {
+            object->RemoveWaitingThread(thread.get());
+        }
+        thread->wait_objects.clear();
+
+        // Invoke the wakeup callback before clearing the wait objects
+        if (thread->wakeup_callback) {
+            resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
+        }
+    }
+
+    if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
+        thread->wait_handle) {
+        ASSERT(thread->status == ThreadStatus::WaitMutex);
+        thread->mutex_wait_address = 0;
+        thread->condvar_wait_address = 0;
+        thread->wait_handle = 0;
+
+        auto lock_owner = thread->lock_owner;
+        // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
+        // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
+        // wasn't awakened due to the mutex already being acquired.
+        if (lock_owner) {
+            lock_owner->RemoveMutexWaiter(thread);
+        }
+    }
+
+    if (thread->arb_wait_address != 0) {
+        ASSERT(thread->status == ThreadStatus::WaitArb);
+        thread->arb_wait_address = 0;
+    }
+
+    if (resume) {
+        thread->ResumeFromWait();
+    }
 }
 
-/// Shutdown the kernel
-void Shutdown() {
-    // Free all kernel objects
-    g_handle_table.Clear();
+/// The timer callback event, called when a timer is fired
+static void TimerCallback(u64 timer_handle, int cycles_late) {
+    const auto proper_handle = static_cast<Handle>(timer_handle);
+    auto& system = Core::System::GetInstance();
+    SharedPtr<Timer> timer = system.Kernel().RetrieveTimerFromCallbackHandleTable(proper_handle);
 
-    Kernel::ThreadingShutdown();
+    if (timer == nullptr) {
+        LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle);
+        return;
+    }
 
-    Kernel::TimersShutdown();
-    Kernel::ResourceLimitsShutdown();
+    timer->Signal(cycles_late);
+}
+
+struct KernelCore::Impl {
+    void Initialize(KernelCore& kernel) {
+        Shutdown();
+
+        InitializeResourceLimits(kernel);
+        InitializeThreads();
+        InitializeTimers();
+    }
+
+    void Shutdown() {
+        next_object_id = 0;
+        next_process_id = 10;
+        next_thread_id = 1;
+
+        process_list.clear();
+
+        handle_table.Clear();
+        resource_limits.fill(nullptr);
+
+        thread_wakeup_callback_handle_table.Clear();
+        thread_wakeup_event_type = nullptr;
+
+        timer_callback_handle_table.Clear();
+        timer_callback_event_type = nullptr;
+    }
+
+    void InitializeResourceLimits(KernelCore& kernel) {
+        // Create the four resource limits that the system uses
+        // Create the APPLICATION resource limit
+        SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create(kernel, "Applications");
+        resource_limit->max_priority = 0x18;
+        resource_limit->max_commit = 0x4000000;
+        resource_limit->max_threads = 0x20;
+        resource_limit->max_events = 0x20;
+        resource_limit->max_mutexes = 0x20;
+        resource_limit->max_semaphores = 0x8;
+        resource_limit->max_timers = 0x8;
+        resource_limit->max_shared_mems = 0x10;
+        resource_limit->max_address_arbiters = 0x2;
+        resource_limit->max_cpu_time = 0x1E;
+        resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit;
+
+        // Create the SYS_APPLET resource limit
+        resource_limit = ResourceLimit::Create(kernel, "System Applets");
+        resource_limit->max_priority = 0x4;
+        resource_limit->max_commit = 0x5E00000;
+        resource_limit->max_threads = 0x1D;
+        resource_limit->max_events = 0xB;
+        resource_limit->max_mutexes = 0x8;
+        resource_limit->max_semaphores = 0x4;
+        resource_limit->max_timers = 0x4;
+        resource_limit->max_shared_mems = 0x8;
+        resource_limit->max_address_arbiters = 0x3;
+        resource_limit->max_cpu_time = 0x2710;
+        resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit;
+
+        // Create the LIB_APPLET resource limit
+        resource_limit = ResourceLimit::Create(kernel, "Library Applets");
+        resource_limit->max_priority = 0x4;
+        resource_limit->max_commit = 0x600000;
+        resource_limit->max_threads = 0xE;
+        resource_limit->max_events = 0x8;
+        resource_limit->max_mutexes = 0x8;
+        resource_limit->max_semaphores = 0x4;
+        resource_limit->max_timers = 0x4;
+        resource_limit->max_shared_mems = 0x8;
+        resource_limit->max_address_arbiters = 0x1;
+        resource_limit->max_cpu_time = 0x2710;
+        resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit;
+
+        // Create the OTHER resource limit
+        resource_limit = ResourceLimit::Create(kernel, "Others");
+        resource_limit->max_priority = 0x4;
+        resource_limit->max_commit = 0x2180000;
+        resource_limit->max_threads = 0xE1;
+        resource_limit->max_events = 0x108;
+        resource_limit->max_mutexes = 0x25;
+        resource_limit->max_semaphores = 0x43;
+        resource_limit->max_timers = 0x2C;
+        resource_limit->max_shared_mems = 0x1F;
+        resource_limit->max_address_arbiters = 0x2D;
+        resource_limit->max_cpu_time = 0x3E8;
+        resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit;
+    }
+
+    void InitializeThreads() {
+        thread_wakeup_event_type =
+            CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
+    }
+
+    void InitializeTimers() {
+        timer_callback_handle_table.Clear();
+        timer_callback_event_type = CoreTiming::RegisterEvent("TimerCallback", TimerCallback);
+    }
+
+    std::atomic<u32> next_object_id{0};
+    // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
+    // reserved for low-level services
+    std::atomic<u32> next_process_id{10};
+    std::atomic<u32> next_thread_id{1};
+
+    // Lists all processes that exist in the current session.
+    std::vector<SharedPtr<Process>> process_list;
+
+    Kernel::HandleTable handle_table;
+    std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
+
+    /// The event type of the generic timer callback event
+    CoreTiming::EventType* timer_callback_event_type = nullptr;
+    // TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future,
+    // allowing us to simply use a pool index or similar.
+    Kernel::HandleTable timer_callback_handle_table;
+
+    CoreTiming::EventType* thread_wakeup_event_type = nullptr;
+    // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future,
+    // allowing us to simply use a pool index or similar.
+    Kernel::HandleTable thread_wakeup_callback_handle_table;
+};
+
+KernelCore::KernelCore() : impl{std::make_unique<Impl>()} {}
+KernelCore::~KernelCore() {
+    Shutdown();
+}
+
+void KernelCore::Initialize() {
+    impl->Initialize(*this);
+}
+
+void KernelCore::Shutdown() {
+    impl->Shutdown();
+}
+
+Kernel::HandleTable& KernelCore::HandleTable() {
+    return impl->handle_table;
+}
+
+const Kernel::HandleTable& KernelCore::HandleTable() const {
+    return impl->handle_table;
+}
+
+SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory(
+    ResourceLimitCategory category) const {
+    return impl->resource_limits.at(static_cast<std::size_t>(category));
+}
+
+SharedPtr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const {
+    return impl->thread_wakeup_callback_handle_table.Get<Thread>(handle);
+}
+
+SharedPtr<Timer> KernelCore::RetrieveTimerFromCallbackHandleTable(Handle handle) const {
+    return impl->timer_callback_handle_table.Get<Timer>(handle);
+}
+
+void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
+    impl->process_list.push_back(std::move(process));
+}
+
+u32 KernelCore::CreateNewObjectID() {
+    return impl->next_object_id++;
+}
+
+u32 KernelCore::CreateNewThreadID() {
+    return impl->next_thread_id++;
+}
+
+u32 KernelCore::CreateNewProcessID() {
+    return impl->next_process_id++;
+}
+
+ResultVal<Handle> KernelCore::CreateTimerCallbackHandle(const SharedPtr<Timer>& timer) {
+    return impl->timer_callback_handle_table.Create(timer);
+}
+
+CoreTiming::EventType* KernelCore::ThreadWakeupCallbackEventType() const {
+    return impl->thread_wakeup_event_type;
+}
+
+CoreTiming::EventType* KernelCore::TimerCallbackEventType() const {
+    return impl->timer_callback_event_type;
+}
+
+Kernel::HandleTable& KernelCore::ThreadWakeupCallbackHandleTable() {
+    return impl->thread_wakeup_callback_handle_table;
+}
+
+const Kernel::HandleTable& KernelCore::ThreadWakeupCallbackHandleTable() const {
+    return impl->thread_wakeup_callback_handle_table;
 }
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 131311472..089e959ac 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -4,14 +4,93 @@
 
 #pragma once
 
-#include "common/common_types.h"
+#include "core/hle/kernel/object.h"
+
+template <typename T>
+class ResultVal;
+
+namespace CoreTiming {
+struct EventType;
+}
 
 namespace Kernel {
 
-/// Initialize the kernel with the specified system mode.
-void Init();
+class HandleTable;
+class Process;
+class ResourceLimit;
+class Thread;
+class Timer;
 
-/// Shutdown the kernel
-void Shutdown();
+enum class ResourceLimitCategory : u8;
+
+/// Represents a single instance of the kernel.
+class KernelCore {
+public:
+    KernelCore();
+    ~KernelCore();
+
+    KernelCore(const KernelCore&) = delete;
+    KernelCore& operator=(const KernelCore&) = delete;
+
+    KernelCore(KernelCore&&) = delete;
+    KernelCore& operator=(KernelCore&&) = delete;
+
+    /// Resets the kernel to a clean slate for use.
+    void Initialize();
+
+    /// Clears all resources in use by the kernel instance.
+    void Shutdown();
+
+    /// Provides a reference to the handle table.
+    Kernel::HandleTable& HandleTable();
+
+    /// Provides a const reference to the handle table.
+    const Kernel::HandleTable& HandleTable() const;
+
+    /// Retrieves a shared pointer to a ResourceLimit identified by the given category.
+    SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const;
+
+    /// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table.
+    SharedPtr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const;
+
+    /// Retrieves a shared pointer to a Timer instance within the timer callback handle table.
+    SharedPtr<Timer> RetrieveTimerFromCallbackHandleTable(Handle handle) const;
+
+    /// Adds the given shared pointer to an internal list of active processes.
+    void AppendNewProcess(SharedPtr<Process> process);
+
+private:
+    friend class Object;
+    friend class Process;
+    friend class Thread;
+    friend class Timer;
+
+    /// Creates a new object ID, incrementing the internal object ID counter.
+    u32 CreateNewObjectID();
+
+    /// Creates a new process ID, incrementing the internal process ID counter;
+    u32 CreateNewProcessID();
+
+    /// Creates a new thread ID, incrementing the internal thread ID counter.
+    u32 CreateNewThreadID();
+
+    /// Creates a timer callback handle for the given timer.
+    ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer);
+
+    /// Retrieves the event type used for thread wakeup callbacks.
+    CoreTiming::EventType* ThreadWakeupCallbackEventType() const;
+
+    /// Retrieves the event type used for timer callbacks.
+    CoreTiming::EventType* TimerCallbackEventType() const;
+
+    /// Provides a reference to the thread wakeup callback handle table.
+    Kernel::HandleTable& ThreadWakeupCallbackHandleTable();
+
+    /// Provides a const reference to the thread wakeup callback handle table.
+    const Kernel::HandleTable& ThreadWakeupCallbackHandleTable() const;
+
+    struct Impl;
+    std::unique_ptr<Impl> impl;
+};
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index cb7f58b35..36bf0b677 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -58,15 +58,15 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
     }
 }
 
-ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
+ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
                              Handle requesting_thread_handle) {
     // The mutex address must be 4-byte aligned
     if ((address % sizeof(u32)) != 0) {
         return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress);
     }
 
-    SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
-    SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
+    SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
+    SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
 
     // TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
     // thread.
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 45268bbe9..81e62d497 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -11,6 +11,7 @@ union ResultCode;
 
 namespace Kernel {
 
+class HandleTable;
 class Thread;
 
 class Mutex final {
@@ -21,8 +22,8 @@ public:
     static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
 
     /// Attempts to acquire a mutex at the specified address.
-    static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
-                                 Handle requesting_thread_handle);
+    static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
+                                 Handle holding_thread_handle, Handle requesting_thread_handle);
 
     /// Releases the mutex at the specified address.
     static ResultCode Release(VAddr address);
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index cdba272f5..d51562d92 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -3,10 +3,12 @@
 // Refer to the license.txt file included.
 
 #include "common/assert.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/object.h"
 
 namespace Kernel {
 
+Object::Object(KernelCore& kernel) : kernel{kernel}, object_id{kernel.CreateNewObjectID()} {}
 Object::~Object() = default;
 
 bool Object::IsWaitable() const {
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 526ac9cc3..b054cbf7d 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -14,6 +14,8 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 using Handle = u32;
 
 enum class HandleType : u32 {
@@ -40,6 +42,7 @@ enum class ResetType {
 
 class Object : NonCopyable {
 public:
+    explicit Object(KernelCore& kernel);
     virtual ~Object();
 
     /// Returns a unique identifier for the object. For debugging purposes only.
@@ -61,15 +64,16 @@ public:
      */
     bool IsWaitable() const;
 
-public:
-    static std::atomic<u32> next_object_id;
+protected:
+    /// The kernel instance this object was created under.
+    KernelCore& kernel;
 
 private:
     friend void intrusive_ptr_add_ref(Object*);
     friend void intrusive_ptr_release(Object*);
 
     std::atomic<u32> ref_count{0};
-    std::atomic<u32> object_id{next_object_id++};
+    std::atomic<u32> object_id{0};
 };
 
 // Special functions used by boost::instrusive_ptr to do automatic ref-counting
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index edf34c5a3..b025e323f 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -8,6 +8,7 @@
 #include "common/common_funcs.h"
 #include "common/logging/log.h"
 #include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
 #include "core/hle/kernel/thread.h"
@@ -16,30 +17,26 @@
 
 namespace Kernel {
 
-// Lists all processes that exist in the current session.
-static std::vector<SharedPtr<Process>> process_list;
-
-SharedPtr<CodeSet> CodeSet::Create(std::string name) {
-    SharedPtr<CodeSet> codeset(new CodeSet);
+SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) {
+    SharedPtr<CodeSet> codeset(new CodeSet(kernel));
     codeset->name = std::move(name);
     return codeset;
 }
 
-CodeSet::CodeSet() {}
-CodeSet::~CodeSet() {}
+CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {}
+CodeSet::~CodeSet() = default;
 
-u32 Process::next_process_id;
-
-SharedPtr<Process> Process::Create(std::string&& name) {
-    SharedPtr<Process> process(new Process);
+SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
+    SharedPtr<Process> process(new Process(kernel));
 
     process->name = std::move(name);
     process->flags.raw = 0;
     process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
     process->status = ProcessStatus::Created;
     process->program_id = 0;
+    process->process_id = kernel.CreateNewProcessID();
 
-    process_list.push_back(process);
+    kernel.AppendNewProcess(process);
     return process;
 }
 
@@ -128,7 +125,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
     vm_manager.LogLayout();
     status = ProcessStatus::Running;
 
-    Kernel::SetupMainThread(entry_point, main_thread_priority, this);
+    Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, this);
 }
 
 void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
@@ -231,22 +228,7 @@ ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) {
     return vm_manager.UnmapRange(dst_addr, size);
 }
 
-Kernel::Process::Process() {}
+Kernel::Process::Process(KernelCore& kernel) : Object{kernel} {}
 Kernel::Process::~Process() {}
 
-void ClearProcessList() {
-    process_list.clear();
-}
-
-SharedPtr<Process> GetProcessById(u32 process_id) {
-    auto itr = std::find_if(
-        process_list.begin(), process_list.end(),
-        [&](const SharedPtr<Process>& process) { return process->process_id == process_id; });
-
-    if (itr == process_list.end())
-        return nullptr;
-
-    return *itr;
-}
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 992689186..1587d40c1 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -19,6 +19,8 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 struct AddressMapping {
     // Address and size must be page-aligned
     VAddr address;
@@ -62,7 +64,7 @@ struct CodeSet final : public Object {
         u32 size = 0;
     };
 
-    static SharedPtr<CodeSet> Create(std::string name);
+    static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name);
 
     std::string GetTypeName() const override {
         return "CodeSet";
@@ -109,13 +111,13 @@ struct CodeSet final : public Object {
     std::string name;
 
 private:
-    CodeSet();
+    explicit CodeSet(KernelCore& kernel);
     ~CodeSet() override;
 };
 
 class Process final : public Object {
 public:
-    static SharedPtr<Process> Create(std::string&& name);
+    static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
 
     std::string GetTypeName() const override {
         return "Process";
@@ -129,8 +131,6 @@ public:
         return HANDLE_TYPE;
     }
 
-    static u32 next_process_id;
-
     /// Title ID corresponding to the process
     u64 program_id;
 
@@ -157,8 +157,8 @@ public:
     /// Current status of the process
     ProcessStatus status;
 
-    /// The id of this process
-    u32 process_id = next_process_id++;
+    /// The ID of this process
+    u32 process_id = 0;
 
     /**
      * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
@@ -206,13 +206,8 @@ public:
     ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
 
 private:
-    Process();
+    explicit Process(KernelCore& kernel);
     ~Process() override;
 };
 
-void ClearProcessList();
-
-/// Retrieves a process from the current list of processes.
-SharedPtr<Process> GetProcessById(u32 process_id);
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 17a3e8a74..b253a680f 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -9,31 +9,16 @@
 
 namespace Kernel {
 
-static SharedPtr<ResourceLimit> resource_limits[4];
+ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {}
+ResourceLimit::~ResourceLimit() = default;
 
-ResourceLimit::ResourceLimit() {}
-ResourceLimit::~ResourceLimit() {}
-
-SharedPtr<ResourceLimit> ResourceLimit::Create(std::string name) {
-    SharedPtr<ResourceLimit> resource_limit(new ResourceLimit);
+SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel, std::string name) {
+    SharedPtr<ResourceLimit> resource_limit(new ResourceLimit(kernel));
 
     resource_limit->name = std::move(name);
     return resource_limit;
 }
 
-SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory category) {
-    switch (category) {
-    case ResourceLimitCategory::APPLICATION:
-    case ResourceLimitCategory::SYS_APPLET:
-    case ResourceLimitCategory::LIB_APPLET:
-    case ResourceLimitCategory::OTHER:
-        return resource_limits[static_cast<u8>(category)];
-    default:
-        LOG_CRITICAL(Kernel, "Unknown resource limit category");
-        UNREACHABLE();
-    }
-}
-
 s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
     switch (resource) {
     case ResourceType::Commit:
@@ -89,66 +74,4 @@ u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
         return 0;
     }
 }
-
-void ResourceLimitsInit() {
-    // Create the four resource limits that the system uses
-    // Create the APPLICATION resource limit
-    SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create("Applications");
-    resource_limit->max_priority = 0x18;
-    resource_limit->max_commit = 0x4000000;
-    resource_limit->max_threads = 0x20;
-    resource_limit->max_events = 0x20;
-    resource_limit->max_mutexes = 0x20;
-    resource_limit->max_semaphores = 0x8;
-    resource_limit->max_timers = 0x8;
-    resource_limit->max_shared_mems = 0x10;
-    resource_limit->max_address_arbiters = 0x2;
-    resource_limit->max_cpu_time = 0x1E;
-    resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit;
-
-    // Create the SYS_APPLET resource limit
-    resource_limit = ResourceLimit::Create("System Applets");
-    resource_limit->max_priority = 0x4;
-    resource_limit->max_commit = 0x5E00000;
-    resource_limit->max_threads = 0x1D;
-    resource_limit->max_events = 0xB;
-    resource_limit->max_mutexes = 0x8;
-    resource_limit->max_semaphores = 0x4;
-    resource_limit->max_timers = 0x4;
-    resource_limit->max_shared_mems = 0x8;
-    resource_limit->max_address_arbiters = 0x3;
-    resource_limit->max_cpu_time = 0x2710;
-    resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit;
-
-    // Create the LIB_APPLET resource limit
-    resource_limit = ResourceLimit::Create("Library Applets");
-    resource_limit->max_priority = 0x4;
-    resource_limit->max_commit = 0x600000;
-    resource_limit->max_threads = 0xE;
-    resource_limit->max_events = 0x8;
-    resource_limit->max_mutexes = 0x8;
-    resource_limit->max_semaphores = 0x4;
-    resource_limit->max_timers = 0x4;
-    resource_limit->max_shared_mems = 0x8;
-    resource_limit->max_address_arbiters = 0x1;
-    resource_limit->max_cpu_time = 0x2710;
-    resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit;
-
-    // Create the OTHER resource limit
-    resource_limit = ResourceLimit::Create("Others");
-    resource_limit->max_priority = 0x4;
-    resource_limit->max_commit = 0x2180000;
-    resource_limit->max_threads = 0xE1;
-    resource_limit->max_events = 0x108;
-    resource_limit->max_mutexes = 0x25;
-    resource_limit->max_semaphores = 0x43;
-    resource_limit->max_timers = 0x2C;
-    resource_limit->max_shared_mems = 0x1F;
-    resource_limit->max_address_arbiters = 0x2D;
-    resource_limit->max_cpu_time = 0x3E8;
-    resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit;
-}
-
-void ResourceLimitsShutdown() {}
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 0fa141db3..219e49562 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -9,6 +9,8 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 enum class ResourceLimitCategory : u8 {
     APPLICATION = 0,
     SYS_APPLET = 1,
@@ -34,14 +36,7 @@ public:
     /**
      * Creates a resource limit object.
      */
-    static SharedPtr<ResourceLimit> Create(std::string name = "Unknown");
-
-    /**
-     * Retrieves the resource limit associated with the specified resource limit category.
-     * @param category The resource limit category
-     * @returns The resource limit associated with the category
-     */
-    static SharedPtr<ResourceLimit> GetForCategory(ResourceLimitCategory category);
+    static SharedPtr<ResourceLimit> Create(KernelCore& kernel, std::string name = "Unknown");
 
     std::string GetTypeName() const override {
         return "ResourceLimit";
@@ -113,14 +108,8 @@ public:
     s32 current_cpu_time = 0;
 
 private:
-    ResourceLimit();
+    explicit ResourceLimit(KernelCore& kernel);
     ~ResourceLimit() override;
 };
 
-/// Initializes the resource limits
-void ResourceLimitsInit();
-
-// Destroys the resource limits
-void ResourceLimitsShutdown();
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 7b6211fd8..3792e3e18 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -13,8 +13,8 @@
 
 namespace Kernel {
 
-ServerPort::ServerPort() {}
-ServerPort::~ServerPort() {}
+ServerPort::ServerPort(KernelCore& kernel) : WaitObject{kernel} {}
+ServerPort::~ServerPort() = default;
 
 ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
     if (pending_sessions.empty()) {
@@ -36,10 +36,10 @@ void ServerPort::Acquire(Thread* thread) {
 }
 
 std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair(
-    u32 max_sessions, std::string name) {
+    KernelCore& kernel, u32 max_sessions, std::string name) {
 
-    SharedPtr<ServerPort> server_port(new ServerPort);
-    SharedPtr<ClientPort> client_port(new ClientPort);
+    SharedPtr<ServerPort> server_port(new ServerPort(kernel));
+    SharedPtr<ClientPort> client_port(new ClientPort(kernel));
 
     server_port->name = name + "_Server";
     client_port->name = name + "_Client";
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 7f6d6b3eb..62fb51349 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -15,6 +15,7 @@
 namespace Kernel {
 
 class ClientPort;
+class KernelCore;
 class ServerSession;
 class SessionRequestHandler;
 
@@ -23,12 +24,13 @@ public:
     /**
      * Creates a pair of ServerPort and an associated ClientPort.
      *
+     * @param kernel The kernel instance to create the port pair under.
      * @param max_sessions Maximum number of sessions to the port
      * @param name Optional name of the ports
      * @return The created port tuple
      */
     static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair(
-        u32 max_sessions, std::string name = "UnknownPort");
+        KernelCore& kernel, u32 max_sessions, std::string name = "UnknownPort");
 
     std::string GetTypeName() const override {
         return "ServerPort";
@@ -69,7 +71,7 @@ public:
     void Acquire(Thread* thread) override;
 
 private:
-    ServerPort();
+    explicit ServerPort(KernelCore& kernel);
     ~ServerPort() override;
 };
 
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 51a1ec160..90c9a5aff 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -20,7 +20,7 @@
 
 namespace Kernel {
 
-ServerSession::ServerSession() = default;
+ServerSession::ServerSession(KernelCore& kernel) : WaitObject{kernel} {}
 ServerSession::~ServerSession() {
     // This destructor will be called automatically when the last ServerSession handle is closed by
     // the emulated application.
@@ -35,8 +35,8 @@ ServerSession::~ServerSession() {
     parent->server = nullptr;
 }
 
-ResultVal<SharedPtr<ServerSession>> ServerSession::Create(std::string name) {
-    SharedPtr<ServerSession> server_session(new ServerSession);
+ResultVal<SharedPtr<ServerSession>> ServerSession::Create(KernelCore& kernel, std::string name) {
+    SharedPtr<ServerSession> server_session(new ServerSession(kernel));
 
     server_session->name = std::move(name);
     server_session->parent = nullptr;
@@ -105,10 +105,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
     // from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
     // similar.
 
+    auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
     Kernel::HLERequestContext context(this);
     u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
-    context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(),
-                                              Kernel::g_handle_table);
+    context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(), handle_table);
 
     ResultCode result = RESULT_SUCCESS;
     // If the session has been converted to a domain, handle the domain request
@@ -160,10 +160,11 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
     return result;
 }
 
-ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name,
+ServerSession::SessionPair ServerSession::CreateSessionPair(KernelCore& kernel,
+                                                            const std::string& name,
                                                             SharedPtr<ClientPort> port) {
-    auto server_session = ServerSession::Create(name + "_Server").Unwrap();
-    SharedPtr<ClientSession> client_session(new ClientSession);
+    auto server_session = ServerSession::Create(kernel, name + "_Server").Unwrap();
+    SharedPtr<ClientSession> client_session(new ClientSession(kernel));
     client_session->name = name + "_Client";
 
     std::shared_ptr<Session> parent(new Session);
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 1a88e66b9..e068db2bf 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -15,13 +15,14 @@
 
 namespace Kernel {
 
-class ClientSession;
 class ClientPort;
+class ClientSession;
+class HLERequestContext;
+class KernelCore;
 class ServerSession;
 class Session;
 class SessionRequestHandler;
 class Thread;
-class HLERequestContext;
 
 /**
  * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS
@@ -50,11 +51,12 @@ public:
 
     /**
      * Creates a pair of ServerSession and an associated ClientSession.
+     * @param kernel      The kernal instance to create the session pair under.
      * @param name        Optional name of the ports.
      * @param client_port Optional The ClientPort that spawned this session.
      * @return The created session tuple
      */
-    static SessionPair CreateSessionPair(const std::string& name = "Unknown",
+    static SessionPair CreateSessionPair(KernelCore& kernel, const std::string& name = "Unknown",
                                          SharedPtr<ClientPort> client_port = nullptr);
 
     /**
@@ -111,16 +113,18 @@ public:
     }
 
 private:
-    ServerSession();
+    explicit ServerSession(KernelCore& kernel);
     ~ServerSession() override;
 
     /**
      * Creates a server session. The server session can have an optional HLE handler,
      * which will be invoked to handle the IPC requests that this session receives.
+     * @param kernel The kernel instance to create this server session under.
      * @param name Optional name of the server session.
      * @return The created server session
      */
-    static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");
+    static ResultVal<SharedPtr<ServerSession>> Create(KernelCore& kernel,
+                                                      std::string name = "Unknown");
 
     /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
     /// object handle.
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index fc168d2b5..abb1d09cd 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -13,14 +13,14 @@
 
 namespace Kernel {
 
-SharedMemory::SharedMemory() {}
-SharedMemory::~SharedMemory() {}
+SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {}
+SharedMemory::~SharedMemory() = default;
 
-SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u64 size,
-                                             MemoryPermission permissions,
+SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Process> owner_process,
+                                             u64 size, MemoryPermission permissions,
                                              MemoryPermission other_permissions, VAddr address,
                                              MemoryRegion region, std::string name) {
-    SharedPtr<SharedMemory> shared_memory(new SharedMemory);
+    SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
 
     shared_memory->owner_process = std::move(owner_process);
     shared_memory->name = std::move(name);
@@ -59,12 +59,10 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u
     return shared_memory;
 }
 
-SharedPtr<SharedMemory> SharedMemory::CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block,
-                                                      u32 offset, u32 size,
-                                                      MemoryPermission permissions,
-                                                      MemoryPermission other_permissions,
-                                                      std::string name) {
-    SharedPtr<SharedMemory> shared_memory(new SharedMemory);
+SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
+    KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size,
+    MemoryPermission permissions, MemoryPermission other_permissions, std::string name) {
+    SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
 
     shared_memory->owner_process = nullptr;
     shared_memory->name = std::move(name);
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index c50fee615..2c729afe3 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -15,6 +15,8 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 /// Permissions for mapped shared memory blocks
 enum class MemoryPermission : u32 {
     None = 0,
@@ -32,6 +34,7 @@ class SharedMemory final : public Object {
 public:
     /**
      * Creates a shared memory object.
+     * @param kernel The kernel instance to create a shared memory instance under.
      * @param owner_process Process that created this shared memory object.
      * @param size Size of the memory block. Must be page-aligned.
      * @param permissions Permission restrictions applied to the process which created the block.
@@ -42,14 +45,15 @@ public:
      * linear heap.
      * @param name Optional object name, used for debugging purposes.
      */
-    static SharedPtr<SharedMemory> Create(SharedPtr<Process> owner_process, u64 size,
-                                          MemoryPermission permissions,
+    static SharedPtr<SharedMemory> Create(KernelCore& kernel, SharedPtr<Process> owner_process,
+                                          u64 size, MemoryPermission permissions,
                                           MemoryPermission other_permissions, VAddr address = 0,
                                           MemoryRegion region = MemoryRegion::BASE,
                                           std::string name = "Unknown");
 
     /**
      * Creates a shared memory object from a block of memory managed by an HLE applet.
+     * @param kernel The kernel instance to create a shared memory instance under.
      * @param heap_block Heap block of the HLE applet.
      * @param offset The offset into the heap block that the SharedMemory will map.
      * @param size Size of the memory block. Must be page-aligned.
@@ -58,7 +62,8 @@ public:
      * block.
      * @param name Optional object name, used for debugging purposes.
      */
-    static SharedPtr<SharedMemory> CreateForApplet(std::shared_ptr<std::vector<u8>> heap_block,
+    static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel,
+                                                   std::shared_ptr<std::vector<u8>> heap_block,
                                                    u32 offset, u32 size,
                                                    MemoryPermission permissions,
                                                    MemoryPermission other_permissions,
@@ -125,7 +130,7 @@ public:
     std::string name;
 
 private:
-    SharedMemory();
+    explicit SharedMemory(KernelCore& kernel);
     ~SharedMemory() override;
 };
 
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index cb6253398..099d1053f 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -87,13 +87,15 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
     CASCADE_RESULT(client_session, client_port->Connect());
 
     // Return the client session
-    CASCADE_RESULT(*out_handle, g_handle_table.Create(client_session));
+    auto& kernel = Core::System::GetInstance().Kernel();
+    CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session));
     return RESULT_SUCCESS;
 }
 
 /// Makes a blocking IPC call to an OS service.
 static ResultCode SendSyncRequest(Handle handle) {
-    SharedPtr<ClientSession> session = g_handle_table.Get<ClientSession>(handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<ClientSession> session = kernel.HandleTable().Get<ClientSession>(handle);
     if (!session) {
         LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
         return ERR_INVALID_HANDLE;
@@ -112,7 +114,8 @@ static ResultCode SendSyncRequest(Handle handle) {
 static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
     LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
 
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     if (!thread) {
         return ERR_INVALID_HANDLE;
     }
@@ -125,7 +128,8 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
 static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
     LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
 
-    const SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
     if (!process) {
         return ERR_INVALID_HANDLE;
     }
@@ -168,10 +172,11 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
 
     using ObjectPtr = SharedPtr<WaitObject>;
     std::vector<ObjectPtr> objects(handle_count);
+    auto& kernel = Core::System::GetInstance().Kernel();
 
     for (u64 i = 0; i < handle_count; ++i) {
         const Handle handle = Memory::Read32(handles_address + i * sizeof(Handle));
-        const auto object = g_handle_table.Get<WaitObject>(handle);
+        const auto object = kernel.HandleTable().Get<WaitObject>(handle);
 
         if (object == nullptr) {
             return ERR_INVALID_HANDLE;
@@ -219,7 +224,8 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
 static ResultCode CancelSynchronization(Handle thread_handle) {
     LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
 
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     if (!thread) {
         return ERR_INVALID_HANDLE;
     }
@@ -239,7 +245,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
               "requesting_current_thread_handle=0x{:08X}",
               holding_thread_handle, mutex_addr, requesting_thread_handle);
 
-    return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle);
+    auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+    return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
+                             requesting_thread_handle);
 }
 
 /// Unlock a mutex
@@ -352,7 +360,8 @@ static ResultCode GetThreadContext(Handle handle, VAddr addr) {
 
 /// Gets the priority for the specified thread
 static ResultCode GetThreadPriority(u32* priority, Handle handle) {
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
     if (!thread)
         return ERR_INVALID_HANDLE;
 
@@ -366,7 +375,8 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
         return ERR_OUT_OF_RANGE;
     }
 
-    SharedPtr<Thread> thread = g_handle_table.Get<Thread>(handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
     if (!thread)
         return ERR_INVALID_HANDLE;
 
@@ -395,7 +405,8 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
               "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
               shared_memory_handle, addr, size, permissions);
 
-    SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
     if (!shared_memory) {
         return ERR_INVALID_HANDLE;
     }
@@ -423,7 +434,8 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
     LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
                 shared_memory_handle, addr, size);
 
-    SharedPtr<SharedMemory> shared_memory = g_handle_table.Get<SharedMemory>(shared_memory_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
 
     return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
 }
@@ -431,7 +443,9 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
 /// Query process memory
 static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
                                      Handle process_handle, u64 addr) {
-    SharedPtr<Process> process = g_handle_table.Get<Process>(process_handle);
+
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
     if (!process) {
         return ERR_INVALID_HANDLE;
     }
@@ -528,10 +542,11 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
         break;
     }
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     CASCADE_RESULT(SharedPtr<Thread> thread,
-                   Thread::Create(name, entry_point, priority, arg, processor_id, stack_top,
+                   Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
                                   Core::CurrentProcess()));
-    CASCADE_RESULT(thread->guest_handle, g_handle_table.Create(thread));
+    CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread));
     *out_handle = thread->guest_handle;
 
     Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
@@ -548,7 +563,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
 static ResultCode StartThread(Handle thread_handle) {
     LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
 
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     if (!thread) {
         return ERR_INVALID_HANDLE;
     }
@@ -595,7 +611,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
         "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
         mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
 
-    SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     ASSERT(thread);
 
     CASCADE_CODE(Mutex::Release(mutex_addr));
@@ -704,8 +721,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
                                                mutex_val | Mutex::MutexHasWaitersFlag));
 
             // The mutex is already owned by some other thread, make this thread wait on it.
+            auto& kernel = Core::System::GetInstance().Kernel();
             Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
-            auto owner = g_handle_table.Get<Thread>(owner_handle);
+            auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
             ASSERT(owner);
             ASSERT(thread->status == ThreadStatus::WaitMutex);
             thread->wakeup_callback = nullptr;
@@ -783,14 +801,20 @@ static u64 GetSystemTick() {
 /// Close a handle
 static ResultCode CloseHandle(Handle handle) {
     LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
-    return g_handle_table.Close(handle);
+
+    auto& kernel = Core::System::GetInstance().Kernel();
+    return kernel.HandleTable().Close(handle);
 }
 
 /// Reset an event
 static ResultCode ResetSignal(Handle handle) {
     LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle);
-    auto event = g_handle_table.Get<Event>(handle);
+
+    auto& kernel = Core::System::GetInstance().Kernel();
+    auto event = kernel.HandleTable().Get<Event>(handle);
+
     ASSERT(event != nullptr);
+
     event->Clear();
     return RESULT_SUCCESS;
 }
@@ -806,7 +830,8 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
 static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
     LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
 
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     if (!thread) {
         return ERR_INVALID_HANDLE;
     }
@@ -821,7 +846,8 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
     LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
               mask, core);
 
-    const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
     if (!thread) {
         return ERR_INVALID_HANDLE;
     }
@@ -861,19 +887,23 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
                                      u32 remote_permissions) {
     LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size,
               local_permissions, remote_permissions);
-    auto sharedMemHandle =
-        SharedMemory::Create(g_handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
+
+    auto& kernel = Core::System::GetInstance().Kernel();
+    auto& handle_table = kernel.HandleTable();
+    auto shared_mem_handle =
+        SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
                              static_cast<MemoryPermission>(local_permissions),
                              static_cast<MemoryPermission>(remote_permissions));
 
-    CASCADE_RESULT(*handle, g_handle_table.Create(sharedMemHandle));
+    CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
     return RESULT_SUCCESS;
 }
 
 static ResultCode ClearEvent(Handle handle) {
     LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
 
-    SharedPtr<Event> evt = g_handle_table.Get<Event>(handle);
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<Event> evt = kernel.HandleTable().Get<Event>(handle);
     if (evt == nullptr)
         return ERR_INVALID_HANDLE;
     evt->Clear();
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 4ffd8d5cc..520ea0853 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -20,6 +20,7 @@
 #include "core/core_timing_util.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/thread.h"
@@ -29,9 +30,6 @@
 
 namespace Kernel {
 
-/// Event type for the thread wake up event
-static CoreTiming::EventType* ThreadWakeupEventType = nullptr;
-
 bool Thread::ShouldWait(Thread* thread) const {
     return status != ThreadStatus::Dead;
 }
@@ -40,32 +38,17 @@ void Thread::Acquire(Thread* thread) {
     ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
 }
 
-// TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing
-//               us to simply use a pool index or similar.
-static Kernel::HandleTable wakeup_callback_handle_table;
-
-// The first available thread id at startup
-static u32 next_thread_id;
-
-/**
- * Creates a new thread ID
- * @return The new thread ID
- */
-inline static u32 const NewThreadId() {
-    return next_thread_id++;
-}
-
-Thread::Thread() {}
-Thread::~Thread() {}
+Thread::Thread(KernelCore& kernel) : WaitObject{kernel} {}
+Thread::~Thread() = default;
 
 void Thread::Stop() {
     // Cancel any outstanding wakeup events for this thread
-    CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
-    wakeup_callback_handle_table.Close(callback_handle);
+    CoreTiming::UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), callback_handle);
+    kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
     callback_handle = 0;
 
     // Clean up thread from ready queue
-    // This is only needed when the thread is termintated forcefully (SVC TerminateProcess)
+    // This is only needed when the thread is terminated forcefully (SVC TerminateProcess)
     if (status == ThreadStatus::Ready) {
         scheduler->UnscheduleThread(this, current_priority);
     }
@@ -98,63 +81,6 @@ void ExitCurrentThread() {
     Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
 }
 
-/**
- * Callback that will wake up the thread it was scheduled for
- * @param thread_handle The handle of the thread that's been awoken
- * @param cycles_late The number of CPU cycles that have passed since the desired wakeup time
- */
-static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
-    const auto proper_handle = static_cast<Handle>(thread_handle);
-
-    // Lock the global kernel mutex when we enter the kernel HLE.
-    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
-
-    SharedPtr<Thread> thread = wakeup_callback_handle_table.Get<Thread>(proper_handle);
-    if (thread == nullptr) {
-        LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle);
-        return;
-    }
-
-    bool resume = true;
-
-    if (thread->status == ThreadStatus::WaitSynchAny ||
-        thread->status == ThreadStatus::WaitSynchAll ||
-        thread->status == ThreadStatus::WaitHLEEvent) {
-        // Remove the thread from each of its waiting objects' waitlists
-        for (auto& object : thread->wait_objects)
-            object->RemoveWaitingThread(thread.get());
-        thread->wait_objects.clear();
-
-        // Invoke the wakeup callback before clearing the wait objects
-        if (thread->wakeup_callback)
-            resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
-    }
-
-    if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
-        thread->wait_handle) {
-        ASSERT(thread->status == ThreadStatus::WaitMutex);
-        thread->mutex_wait_address = 0;
-        thread->condvar_wait_address = 0;
-        thread->wait_handle = 0;
-
-        auto lock_owner = thread->lock_owner;
-        // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
-        // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
-        // wasn't awakened due to the mutex already being acquired.
-        if (lock_owner) {
-            lock_owner->RemoveMutexWaiter(thread);
-        }
-    }
-
-    if (thread->arb_wait_address != 0) {
-        ASSERT(thread->status == ThreadStatus::WaitArb);
-        thread->arb_wait_address = 0;
-    }
-
-    if (resume)
-        thread->ResumeFromWait();
-}
-
 void Thread::WakeAfterDelay(s64 nanoseconds) {
     // Don't schedule a wakeup if the thread wants to wait forever
     if (nanoseconds == -1)
@@ -162,12 +88,12 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
 
     // This function might be called from any thread so we have to be cautious and use the
     // thread-safe version of ScheduleEvent.
-    CoreTiming::ScheduleEventThreadsafe(CoreTiming::nsToCycles(nanoseconds), ThreadWakeupEventType,
-                                        callback_handle);
+    CoreTiming::ScheduleEventThreadsafe(CoreTiming::nsToCycles(nanoseconds),
+                                        kernel.ThreadWakeupCallbackEventType(), callback_handle);
 }
 
 void Thread::CancelWakeupTimer() {
-    CoreTiming::UnscheduleEventThreadsafe(ThreadWakeupEventType, callback_handle);
+    CoreTiming::UnscheduleEventThreadsafe(kernel.ThreadWakeupCallbackEventType(), callback_handle);
 }
 
 static boost::optional<s32> GetNextProcessorId(u64 mask) {
@@ -294,9 +220,9 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd
     context.fpscr = 0;
 }
 
-ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, u32 priority,
-                                            u64 arg, s32 processor_id, VAddr stack_top,
-                                            SharedPtr<Process> owner_process) {
+ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point,
+                                            u32 priority, u64 arg, s32 processor_id,
+                                            VAddr stack_top, SharedPtr<Process> owner_process) {
     // Check if priority is in ranged. Lowest priority -> highest priority id.
     if (priority > THREADPRIO_LOWEST) {
         LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
@@ -316,9 +242,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
         return ResultCode(-1);
     }
 
-    SharedPtr<Thread> thread(new Thread);
+    SharedPtr<Thread> thread(new Thread(kernel));
 
-    thread->thread_id = NewThreadId();
+    thread->thread_id = kernel.CreateNewThreadID();
     thread->status = ThreadStatus::Dormant;
     thread->entry_point = entry_point;
     thread->stack_top = stack_top;
@@ -333,7 +259,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
     thread->condvar_wait_address = 0;
     thread->wait_handle = 0;
     thread->name = std::move(name);
-    thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
+    thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
     thread->owner_process = owner_process;
     thread->scheduler = Core::System::GetInstance().Scheduler(processor_id);
     thread->scheduler->AddThread(thread, priority);
@@ -383,19 +309,19 @@ void Thread::BoostPriority(u32 priority) {
     current_priority = priority;
 }
 
-SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
+SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority,
                                   SharedPtr<Process> owner_process) {
     // Setup page table so we can write to memory
     SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
 
     // Initialize new "main" thread
-    auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
+    auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0,
                                      Memory::STACK_AREA_VADDR_END, std::move(owner_process));
 
     SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
 
     // Register 1 must be a handle to the main thread
-    thread->guest_handle = Kernel::g_handle_table.Create(thread).Unwrap();
+    thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap();
 
     thread->context.cpu_registers[1] = thread->guest_handle;
 
@@ -528,13 +454,4 @@ Thread* GetCurrentThread() {
     return Core::System::GetInstance().CurrentScheduler().GetCurrentThread();
 }
 
-void ThreadingInit() {
-    ThreadWakeupEventType = CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
-    next_thread_id = 1;
-}
-
-void ThreadingShutdown() {
-    Kernel::ClearProcessList();
-}
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 06edc296d..20f50458b 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -56,6 +56,7 @@ enum class ThreadWakeupReason {
 
 namespace Kernel {
 
+class KernelCore;
 class Process;
 class Scheduler;
 
@@ -63,6 +64,7 @@ class Thread final : public WaitObject {
 public:
     /**
      * Creates and returns a new thread. The new thread is immediately scheduled
+     * @param kernel The kernel instance this thread will be created under.
      * @param name The friendly name desired for the thread
      * @param entry_point The address at which the thread should start execution
      * @param priority The thread's priority
@@ -72,8 +74,9 @@ public:
      * @param owner_process The parent process for the thread
      * @return A shared pointer to the newly created thread
      */
-    static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, u32 priority,
-                                               u64 arg, s32 processor_id, VAddr stack_top,
+    static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name,
+                                               VAddr entry_point, u32 priority, u64 arg,
+                                               s32 processor_id, VAddr stack_top,
                                                SharedPtr<Process> owner_process);
 
     std::string GetName() const override {
@@ -263,7 +266,7 @@ public:
     u64 affinity_mask{0x1};
 
 private:
-    Thread();
+    explicit Thread(KernelCore& kernel);
     ~Thread() override;
 
     std::shared_ptr<std::vector<u8>> tls_memory = std::make_shared<std::vector<u8>>();
@@ -271,12 +274,13 @@ private:
 
 /**
  * Sets up the primary application thread
+ * @param kernel The kernel instance to create the main thread under.
  * @param entry_point The address at which the thread should start execution
  * @param priority The priority to give the main thread
  * @param owner_process The parent process for the main thread
  * @return A shared pointer to the main thread
  */
-SharedPtr<Thread> SetupMainThread(VAddr entry_point, u32 priority,
+SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority,
                                   SharedPtr<Process> owner_process);
 
 /**
@@ -294,14 +298,4 @@ void WaitCurrentThread_Sleep();
  */
 void ExitCurrentThread();
 
-/**
- * Initialize threading
- */
-void ThreadingInit();
-
-/**
- * Shutdown threading
- */
-void ThreadingShutdown();
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 282360745..6957b16e0 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -2,36 +2,31 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <cinttypes>
 #include "common/assert.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing_util.h"
 #include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/timer.h"
 
 namespace Kernel {
 
-/// The event type of the generic timer callback event
-static CoreTiming::EventType* timer_callback_event_type = nullptr;
-// TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future, allowing
-//               us to simply use a pool index or similar.
-static Kernel::HandleTable timer_callback_handle_table;
+Timer::Timer(KernelCore& kernel) : WaitObject{kernel} {}
+Timer::~Timer() = default;
 
-Timer::Timer() {}
-Timer::~Timer() {}
-
-SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
-    SharedPtr<Timer> timer(new Timer);
+SharedPtr<Timer> Timer::Create(KernelCore& kernel, ResetType reset_type, std::string name) {
+    SharedPtr<Timer> timer(new Timer(kernel));
 
     timer->reset_type = reset_type;
     timer->signaled = false;
     timer->name = std::move(name);
     timer->initial_delay = 0;
     timer->interval_delay = 0;
-    timer->callback_handle = timer_callback_handle_table.Create(timer).Unwrap();
+    timer->callback_handle = kernel.CreateTimerCallbackHandle(timer).Unwrap();
 
     return timer;
 }
@@ -58,13 +53,13 @@ void Timer::Set(s64 initial, s64 interval) {
         // Immediately invoke the callback
         Signal(0);
     } else {
-        CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), timer_callback_event_type,
+        CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), kernel.TimerCallbackEventType(),
                                   callback_handle);
     }
 }
 
 void Timer::Cancel() {
-    CoreTiming::UnscheduleEvent(timer_callback_event_type, callback_handle);
+    CoreTiming::UnscheduleEvent(kernel.TimerCallbackEventType(), callback_handle);
 }
 
 void Timer::Clear() {
@@ -89,28 +84,8 @@ void Timer::Signal(int cycles_late) {
     if (interval_delay != 0) {
         // Reschedule the timer with the interval delay
         CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(interval_delay) - cycles_late,
-                                  timer_callback_event_type, callback_handle);
+                                  kernel.TimerCallbackEventType(), callback_handle);
     }
 }
 
-/// The timer callback event, called when a timer is fired
-static void TimerCallback(u64 timer_handle, int cycles_late) {
-    SharedPtr<Timer> timer =
-        timer_callback_handle_table.Get<Timer>(static_cast<Handle>(timer_handle));
-
-    if (timer == nullptr) {
-        LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle);
-        return;
-    }
-
-    timer->Signal(cycles_late);
-}
-
-void TimersInit() {
-    timer_callback_handle_table.Clear();
-    timer_callback_event_type = CoreTiming::RegisterEvent("TimerCallback", TimerCallback);
-}
-
-void TimersShutdown() {}
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index 4dddc67e0..12915c1b1 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -10,15 +10,19 @@
 
 namespace Kernel {
 
+class KernelCore;
+
 class Timer final : public WaitObject {
 public:
     /**
      * Creates a timer
+     * @param kernel The kernel instance to create the timer callback handle for.
      * @param reset_type ResetType describing how to create the timer
      * @param name Optional name of timer
      * @return The created Timer
      */
-    static SharedPtr<Timer> Create(ResetType reset_type, std::string name = "Unknown");
+    static SharedPtr<Timer> Create(KernelCore& kernel, ResetType reset_type,
+                                   std::string name = "Unknown");
 
     std::string GetTypeName() const override {
         return "Timer";
@@ -68,7 +72,7 @@ public:
     void Signal(int cycles_late);
 
 private:
-    Timer();
+    explicit Timer(KernelCore& kernel);
     ~Timer() override;
 
     ResetType reset_type; ///< The ResetType of this timer
@@ -83,9 +87,4 @@ private:
     Handle callback_handle;
 };
 
-/// Initializes the required variables for timers
-void TimersInit();
-/// Tears down the timer variables
-void TimersShutdown();
-
 } // namespace Kernel
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 7681cdee7..eef00b729 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -12,6 +12,9 @@
 
 namespace Kernel {
 
+WaitObject::WaitObject(KernelCore& kernel) : Object{kernel} {}
+WaitObject::~WaitObject() = default;
+
 void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) {
     auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
     if (itr == waiting_threads.end())
diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h
index b5fbc647b..0bd97133c 100644
--- a/src/core/hle/kernel/wait_object.h
+++ b/src/core/hle/kernel/wait_object.h
@@ -11,11 +11,15 @@
 
 namespace Kernel {
 
+class KernelCore;
 class Thread;
 
 /// Class that represents a Kernel object that a thread can be waiting on
 class WaitObject : public Object {
 public:
+    explicit WaitObject(KernelCore& kernel);
+    ~WaitObject() override;
+
     /**
      * Check if the specified thread should wait until the object is available
      * @param thread The thread about which we're deciding.
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7e3cf6d58..818c03e0f 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -160,8 +160,9 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
     };
     RegisterHandlers(functions);
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     launchable_event =
-        Kernel::Event::Create(Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
+        Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
 }
 
 void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
@@ -332,7 +333,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
     };
     RegisterHandlers(functions);
 
-    event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
 }
 
 void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) {
@@ -505,7 +507,8 @@ public:
         };
         RegisterHandlers(functions);
 
-        state_changed_event = Kernel::Event::Create(Kernel::ResetType::OneShot,
+        auto& kernel = Core::System::GetInstance().Kernel();
+        state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
                                                     "ILibraryAppletAccessor:StateChangedEvent");
     }
 
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index ce709ccf4..5f370bbdf 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -47,7 +47,9 @@ public:
         RegisterHandlers(functions);
 
         // This is the event handle used to check if the audio buffer was released
-        buffer_event = Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
+        auto& kernel = Core::System::GetInstance().Kernel();
+        buffer_event =
+            Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
 
         stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count,
                                        "IAudioOut", [=]() { buffer_event->Signal(); });
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 9e75eb3a6..016db7c82 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -35,8 +35,9 @@ public:
         };
         RegisterHandlers(functions);
 
+        auto& kernel = Core::System::GetInstance().Kernel();
         system_event =
-            Kernel::Event::Create(Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent");
+            Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent");
         renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event);
     }
 
@@ -121,8 +122,9 @@ public:
         };
         RegisterHandlers(functions);
 
-        buffer_event =
-            Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
+        auto& kernel = Core::System::GetInstance().Kernel();
+        buffer_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
+                                             "IAudioOutBufferReleasedEvent");
     }
 
 private:
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index c0ba330dc..0d31abe8b 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -4,6 +4,7 @@
 
 #include <atomic>
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing_util.h"
 #include "core/frontend/emu_window.h"
@@ -35,9 +36,10 @@ public:
         };
         RegisterHandlers(functions);
 
+        auto& kernel = Core::System::GetInstance().Kernel();
         shared_mem = Kernel::SharedMemory::Create(
-            nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read,
-            0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
+            kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite,
+            Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
 
         // Register update callbacks
         pad_update_event = CoreTiming::RegisterEvent(
@@ -402,7 +404,8 @@ public:
 
         RegisterHandlers(functions);
 
-        event = Kernel::Event::Create(Kernel::ResetType::OneShot, "hid:EventHandle");
+        auto& kernel = Core::System::GetInstance().Kernel();
+        event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle");
     }
     ~Hid() = default;
 
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 56b05e9e8..4f7543af5 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -46,11 +46,13 @@ public:
         };
         RegisterHandlers(functions);
 
-        activate_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:ActivateEvent");
+        auto& kernel = Core::System::GetInstance().Kernel();
+        activate_event =
+            Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:ActivateEvent");
         deactivate_event =
-            Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
-        availability_change_event =
-            Kernel::Event::Create(Kernel::ResetType::OneShot, "IUser:AvailabilityChangeEvent");
+            Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
+        availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
+                                                          "IUser:AvailabilityChangeEvent");
     }
 
 private:
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index cfe8d9178..ed4f5f539 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/service/nifm/nifm.h"
@@ -54,8 +55,9 @@ public:
         };
         RegisterHandlers(functions);
 
-        event1 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event1");
-        event2 = Kernel::Event::Create(Kernel::ResetType::OneShot, "IRequest:Event2");
+        auto& kernel = Core::System::GetInstance().Kernel();
+        event1 = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IRequest:Event1");
+        event2 = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IRequest:Event2");
     }
 
 private:
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 923a52cc5..51638793d 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -266,8 +266,9 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
         SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared);
 
     // Create shared font memory object
+    auto& kernel = Core::System::GetInstance().Kernel();
     shared_font_mem = Kernel::SharedMemory::Create(
-        Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
+        kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite,
         Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE,
         "PL_U:shared_font_mem");
 
diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp
index 1b497b814..634ab9196 100644
--- a/src/core/hle/service/nvdrv/interface.cpp
+++ b/src/core/hle/service/nvdrv/interface.cpp
@@ -4,6 +4,7 @@
 
 #include <cinttypes>
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/service/nvdrv/interface.h"
@@ -107,7 +108,8 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name)
     };
     RegisterHandlers(functions);
 
-    query_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "NVDRV::query_event");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    query_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "NVDRV::query_event");
 }
 
 } // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index ef5713a71..8d8962276 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -6,14 +6,16 @@
 
 #include "common/assert.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/hle/service/nvflinger/buffer_queue.h"
 
 namespace Service {
 namespace NVFlinger {
 
 BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
+    auto& kernel = Core::System::GetInstance().Kernel();
     buffer_wait_event =
-        Kernel::Event::Create(Kernel::ResetType::Sticky, "BufferQueue NativeHandle");
+        Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "BufferQueue NativeHandle");
 }
 
 void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index a26a5f812..3996c24fe 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -161,7 +161,8 @@ void NVFlinger::Compose() {
 Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {}
 
 Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) {
-    vsync_event = Kernel::Event::Create(Kernel::ResetType::Pulse, "Display VSync Event");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    vsync_event = Kernel::Event::Create(kernel, Kernel::ResetType::Pulse, "Display VSync Event");
 }
 
 } // namespace Service::NVFlinger
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 11951adaf..8fb907072 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -107,19 +107,24 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
 
 void ServiceFrameworkBase::InstallAsNamedPort() {
     ASSERT(port == nullptr);
+
+    auto& kernel = Core::System::GetInstance().Kernel();
     SharedPtr<ServerPort> server_port;
     SharedPtr<ClientPort> client_port;
-    std::tie(server_port, client_port) = ServerPort::CreatePortPair(max_sessions, service_name);
+    std::tie(server_port, client_port) =
+        ServerPort::CreatePortPair(kernel, max_sessions, service_name);
     server_port->SetHleHandler(shared_from_this());
     AddNamedPort(service_name, std::move(client_port));
 }
 
 Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
     ASSERT(port == nullptr);
+
+    auto& kernel = Core::System::GetInstance().Kernel();
     Kernel::SharedPtr<Kernel::ServerPort> server_port;
     Kernel::SharedPtr<Kernel::ClientPort> client_port;
     std::tie(server_port, client_port) =
-        Kernel::ServerPort::CreatePortPair(max_sessions, service_name);
+        Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
     port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap();
     port->SetHleHandler(shared_from_this());
     return client_port;
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index f22a2a79f..b240d7eed 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -4,6 +4,7 @@
 
 #include <tuple>
 #include "common/assert.h"
+#include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/client_session.h"
@@ -47,9 +48,11 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
     if (registered_services.find(name) != registered_services.end())
         return ERR_ALREADY_REGISTERED;
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     Kernel::SharedPtr<Kernel::ServerPort> server_port;
     Kernel::SharedPtr<Kernel::ClientPort> client_port;
-    std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name);
+    std::tie(server_port, client_port) =
+        Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
 
     registered_services.emplace(std::move(name), std::move(client_port));
     return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 4c79d7902..a53fa6e00 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -6,6 +6,7 @@
 #include "common/common_funcs.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/romfs_factory.h"
@@ -117,10 +118,11 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
         }
     }
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     process->program_id = metadata.GetTitleID();
     process->svc_access_mask.set();
     process->resource_limit =
-        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
+        kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
     process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(),
                  metadata.GetMainThreadStackSize());
 
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 6420a7f11..3702a8478 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -9,6 +9,7 @@
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
 #include "core/loader/elf.h"
@@ -300,7 +301,8 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) {
     std::vector<u8> program_image(total_image_size);
     size_t current_image_position = 0;
 
-    SharedPtr<CodeSet> codeset = CodeSet::Create("");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, "");
 
     for (unsigned int i = 0; i < header->e_phnum; ++i) {
         Elf32_Phdr* p = &segments[i];
@@ -400,8 +402,9 @@ ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     process->svc_access_mask.set();
 
     // Attach the default resource limit (APPLICATION) to the process
+    auto& kernel = Core::System::GetInstance().Kernel();
     process->resource_limit =
-        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
+        kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
 
     process->Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
 
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 2179cf2ea..00205d1d2 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -136,7 +136,8 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
     }
 
     // Build program image
-    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
     std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
     if (program_image.size() != PageAlignSize(nro_header.file_size)) {
         return {};
@@ -185,9 +186,10 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
         return ResultStatus::ErrorLoadingNRO;
     }
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     process->svc_access_mask.set();
     process->resource_limit =
-        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
+        kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
     process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
 
     is_loaded = true;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index a94558ac5..0c992d662 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -100,7 +100,8 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
         return {};
 
     // Build program image
-    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create("");
+    auto& kernel = Core::System::GetInstance().Kernel();
+    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
     std::vector<u8> program_image;
     for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
         const std::vector<u8> compressed_data =
@@ -151,9 +152,10 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     LoadModule(file, Memory::PROCESS_IMAGE_VADDR);
     LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), Memory::PROCESS_IMAGE_VADDR);
 
+    auto& kernel = Core::System::GetInstance().Kernel();
     process->svc_access_mask.set();
     process->resource_limit =
-        Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
+        kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION);
     process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
 
     is_loaded = true;
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 539746246..038d57b3a 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -13,7 +13,7 @@ namespace ArmTests {
 TestEnvironment::TestEnvironment(bool mutable_memory_)
     : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
 
-    Core::CurrentProcess() = Kernel::Process::Create("");
+    Core::CurrentProcess() = Kernel::Process::Create(kernel, "");
     page_table = &Core::CurrentProcess()->vm_manager.page_table;
 
     page_table->pointers.fill(nullptr);
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 7fdbda494..e4b6df194 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "common/common_types.h"
+#include "core/hle/kernel/kernel.h"
 #include "core/memory_hook.h"
 
 namespace Memory {
@@ -86,6 +87,7 @@ private:
     std::shared_ptr<TestMemory> test_memory;
     std::vector<WriteRecord> write_records;
     Memory::PageTable* page_table = nullptr;
+    Kernel::KernelCore kernel;
 };
 
 } // namespace ArmTests
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index d0926d723..eac0c05f2 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -77,9 +77,11 @@ QString WaitTreeText::GetText() const {
 }
 
 WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
+    auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+
     mutex_value = Memory::Read32(mutex_address);
     owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
-    owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle);
+    owner = handle_table.Get<Kernel::Thread>(owner_handle);
 }
 
 QString WaitTreeMutexInfo::GetText() const {