diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/EditorInlayLensManager.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/EditorInlayLensManager.kt
index 4b32943..822f3ef 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/EditorInlayLensManager.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/EditorInlayLensManager.kt
@@ -4,7 +4,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo
 import com.intellij.openapi.editor.Editor
 import com.intellij.openapi.editor.Inlay
 import com.intellij.openapi.editor.InlayProperties
-import com.intellij.openapi.editor.ex.RangeHighlighterEx
+import com.intellij.openapi.editor.markup.RangeHighlighter
 import com.intellij.openapi.util.Key
 
 /**
@@ -18,6 +18,14 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
 			return editor.getUserData(KEY) ?: EditorInlayLensManager(editor).also { editor.putUserData(KEY, it) }
 		}
 		
+		fun remove(editor: Editor) {
+			val manager = editor.getUserData(KEY)
+			if (manager != null) {
+				manager.hideAll()
+				editor.putUserData(KEY, null)
+			}
+		}
+		
 		private fun updateRenderer(renderer: LensRenderer, info: HighlightInfo) {
 			renderer.text = info.description.takeIf(String::isNotBlank)?.let(::addMissingPeriod) ?: " "
 			renderer.severity = LensSeverity.from(info.severity)
@@ -28,9 +36,9 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
 		}
 	}
 	
-	private val inlays = mutableMapOf<RangeHighlighterEx, Inlay<LensRenderer>>()
+	private val inlays = mutableMapOf<RangeHighlighter, Inlay<LensRenderer>>()
 	
-	fun show(highlighter: RangeHighlighterEx, info: HighlightInfo) {
+	fun show(highlighter: RangeHighlighter, info: HighlightInfo) {
 		val currentInlay = inlays[highlighter]
 		if (currentInlay != null && currentInlay.isValid) {
 			updateRenderer(currentInlay.renderer, info)
@@ -47,7 +55,12 @@ class EditorInlayLensManager private constructor(private val editor: Editor) {
 		}
 	}
 	
-	fun hide(highlighter: RangeHighlighterEx) {
+	fun hide(highlighter: RangeHighlighter) {
 		inlays.remove(highlighter)?.dispose()
 	}
+	
+	fun hideAll() {
+		inlays.values.forEach(Inlay<*>::dispose)
+		inlays.clear()
+	}
 }
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginDisposableService.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginDisposableService.kt
new file mode 100644
index 0000000..59ce4a1
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginDisposableService.kt
@@ -0,0 +1,10 @@
+package com.chylex.intellij.inspectionlens
+
+import com.intellij.openapi.Disposable
+
+/**
+ * Gets automatically disposed when the plugin is unloaded.
+ */
+class InspectionLensPluginDisposableService : Disposable {
+	override fun dispose() {}
+}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginListener.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginListener.kt
new file mode 100644
index 0000000..af0df07
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/InspectionLensPluginListener.kt
@@ -0,0 +1,43 @@
+package com.chylex.intellij.inspectionlens
+
+import com.intellij.ide.plugins.DynamicPluginListener
+import com.intellij.ide.plugins.IdeaPluginDescriptor
+import com.intellij.openapi.fileEditor.FileEditorManager
+import com.intellij.openapi.fileEditor.TextEditor
+import com.intellij.openapi.project.ProjectManager
+
+/**
+ * Handles dynamic plugin loading.
+ * 
+ * On load, it installs the [LensMarkupModelListener] to all open editors.
+ * On unload, it removes all lenses from all open editors.
+ */
+class InspectionLensPluginListener : DynamicPluginListener {
+	companion object {
+		private const val PLUGIN_ID = "com.chylex.intellij.inspectionlens"
+		
+		private inline fun ProjectManager.forEachEditor(action: (TextEditor) -> Unit) {
+			for (project in this.openProjects.filterNot { it.isDisposed }) {
+				val fileEditorManager = FileEditorManager.getInstance(project)
+				
+				for (editor in fileEditorManager.allEditors.filterIsInstance<TextEditor>()) {
+					action(editor)
+				}
+			}
+		}
+	}
+	
+	override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) {
+		if (pluginDescriptor.pluginId.idString == PLUGIN_ID) {
+			ProjectManager.getInstanceIfCreated()?.forEachEditor(LensMarkupModelListener.Companion::install)
+		}
+	}
+	
+	override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) {
+		if (pluginDescriptor.pluginId.idString == PLUGIN_ID) {
+			ProjectManager.getInstanceIfCreated()?.forEachEditor {
+				EditorInlayLensManager.remove(it.editor)
+			}
+		}
+	}
+}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/LensFileEditorListener.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/LensFileEditorListener.kt
index b551d89..402f96a 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/LensFileEditorListener.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/LensFileEditorListener.kt
@@ -1,7 +1,5 @@
 package com.chylex.intellij.inspectionlens
 
-import com.intellij.openapi.editor.ex.MarkupModelEx
-import com.intellij.openapi.editor.impl.DocumentMarkupModel
 import com.intellij.openapi.fileEditor.FileEditorManager
 import com.intellij.openapi.fileEditor.FileEditorManagerListener
 import com.intellij.openapi.fileEditor.TextEditor
@@ -9,18 +7,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
 import com.intellij.openapi.vfs.VirtualFile
 
 /**
- * Listens for newly opened editors, and attaches a [LensMarkupModelListener] to their document model.
+ * Listens for newly opened editors, and installs a [LensMarkupModelListener] on them.
  */
 class LensFileEditorListener : FileEditorManagerListener {
 	override fun fileOpenedSync(source: FileEditorManager, file: VirtualFile, editorsWithProviders: MutableList<FileEditorWithProvider>) {
 		for (editorWrapper in editorsWithProviders) {
 			val fileEditor = editorWrapper.fileEditor
 			if (fileEditor is TextEditor) {
-				val editor = fileEditor.editor
-				val markupModel = DocumentMarkupModel.forDocument(editor.document, editor.project, false)
-				if (markupModel is MarkupModelEx) {
-					markupModel.addMarkupModelListener(fileEditor, LensMarkupModelListener(editor))
-				}
+				LensMarkupModelListener.install(fileEditor)
 			}
 		}
 	}
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/LensMarkupModelListener.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/LensMarkupModelListener.kt
index 984e423..ddd43b1 100644
--- a/src/main/kotlin/com/chylex/intellij/inspectionlens/LensMarkupModelListener.kt
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/LensMarkupModelListener.kt
@@ -1,16 +1,22 @@
 package com.chylex.intellij.inspectionlens
 
+import com.chylex.intellij.inspectionlens.util.MultiParentDisposable
 import com.intellij.codeInsight.daemon.impl.AsyncDescriptionSupplier
 import com.intellij.codeInsight.daemon.impl.HighlightInfo
 import com.intellij.lang.annotation.HighlightSeverity
+import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.ex.MarkupModelEx
 import com.intellij.openapi.editor.ex.RangeHighlighterEx
+import com.intellij.openapi.editor.impl.DocumentMarkupModel
 import com.intellij.openapi.editor.impl.event.MarkupModelListener
+import com.intellij.openapi.editor.markup.RangeHighlighter
+import com.intellij.openapi.fileEditor.TextEditor
 
 /**
  * Listens for inspection highlights and reports them to [EditorInlayLensManager].
  */
-class LensMarkupModelListener(editor: Editor) : MarkupModelListener {
+class LensMarkupModelListener private constructor(editor: Editor) : MarkupModelListener {
 	private val lens = EditorInlayLensManager.getOrCreate(editor)
 	
 	override fun afterAdded(highlighter: RangeHighlighterEx) {
@@ -25,7 +31,7 @@ class LensMarkupModelListener(editor: Editor) : MarkupModelListener {
 		lens.hide(highlighter)
 	}
 	
-	private fun showIfValid(highlighter: RangeHighlighterEx) {
+	private fun showIfValid(highlighter: RangeHighlighter) {
 		if (!highlighter.isValid) {
 			return
 		}
@@ -47,9 +53,35 @@ class LensMarkupModelListener(editor: Editor) : MarkupModelListener {
 		}
 	}
 	
-	private fun showIfNonNullDescription(highlighter: RangeHighlighterEx, info: HighlightInfo) {
+	private fun showIfNonNullDescription(highlighter: RangeHighlighter, info: HighlightInfo) {
 		if (info.description != null) {
 			lens.show(highlighter, info)
 		}
 	}
+	
+	companion object {
+		/**
+		 * Attaches a new [LensMarkupModelListener] to the document model of the provided [TextEditor], and reports all existing inspection highlights to [EditorInlayLensManager].
+		 * 
+		 * The [LensMarkupModelListener] will be disposed when either the [TextEditor] is disposed, or via [InspectionLensPluginDisposableService] when the plugin is unloaded.
+		 */
+		fun install(textEditor: TextEditor) {
+			val editor = textEditor.editor
+			val markupModel = DocumentMarkupModel.forDocument(editor.document, editor.project, false)
+			if (markupModel is MarkupModelEx) {
+				val pluginDisposable = ApplicationManager.getApplication().getService(InspectionLensPluginDisposableService::class.java)
+				
+				val listenerDisposable = MultiParentDisposable()
+				listenerDisposable.registerWithParent(textEditor)
+				listenerDisposable.registerWithParent(pluginDisposable)
+				
+				val listener = LensMarkupModelListener(editor)
+				markupModel.addMarkupModelListener(listenerDisposable.self, listener)
+				
+				for (highlighter in markupModel.allHighlighters) {
+					listener.showIfValid(highlighter)
+				}
+			}
+		}
+	}
 }
diff --git a/src/main/kotlin/com/chylex/intellij/inspectionlens/util/MultiParentDisposable.kt b/src/main/kotlin/com/chylex/intellij/inspectionlens/util/MultiParentDisposable.kt
new file mode 100644
index 0000000..f8837d5
--- /dev/null
+++ b/src/main/kotlin/com/chylex/intellij/inspectionlens/util/MultiParentDisposable.kt
@@ -0,0 +1,18 @@
+package com.chylex.intellij.inspectionlens.util
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.util.Disposer
+import java.lang.ref.WeakReference
+
+/**
+ * A [Disposable] that can have multiple parents, and will be disposed when any parent is disposed.
+ * A [WeakReference] and a lambda will remain in memory for every parent that is not disposed.
+ */
+class MultiParentDisposable {
+	val self = Disposer.newDisposable()
+	
+	fun registerWithParent(parent: Disposable) {
+		val weakSelfReference = WeakReference(self)
+		Disposer.register(parent) { weakSelfReference.get()?.let(Disposer::dispose) }
+	}
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 838c1a2..b2d07b8 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -11,6 +11,14 @@
   
   <depends>com.intellij.modules.platform</depends>
   
+  <extensions defaultExtensionNs="com.intellij">
+    <applicationService serviceImplementation="com.chylex.intellij.inspectionlens.InspectionLensPluginDisposableService" />
+  </extensions>
+  
+  <applicationListeners>
+    <listener class="com.chylex.intellij.inspectionlens.InspectionLensPluginListener" topic="com.intellij.ide.plugins.DynamicPluginListener" />
+  </applicationListeners>
+  
   <projectListeners>
     <listener class="com.chylex.intellij.inspectionlens.LensFileEditorListener" topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" />
   </projectListeners>