@Override
	public void scan() {
		int numDescriptors = virtualMachineDescriptors.size();
		List<VirtualMachineDescriptor> remoteVmList = VirtualMachine.list();
		Set<VirtualMachineDescriptor> toRemove = new HashSet<>(virtualMachineDescriptors);
		Set<VirtualMachineDescriptor> toAdd = new HashSet<>();
		List<CompletableFuture<?>> attachFutures = new ArrayList<>();
		for (VirtualMachineDescriptor descriptor : remoteVmList) {
			// Still active in VM list, keep it.
			toRemove.remove(descriptor);

			// Add if not in the list.
			if (!virtualMachineDescriptors.contains(descriptor)) {
				String label = descriptor.id() + " - " + StringUtil.withEmptyFallback(descriptor.displayName(), "?");
				int pid = mapToPid(descriptor);
				if (pid == currentPid) // skip self
					continue;

				// Using futures for attach in case one of the VM's decides to hang on response.
				// Using 'orTimeout' we can prevent such hangs from affecting us.
				attachFutures.add(ThreadUtil.run(() -> {
					try {
						AttachProvider provider = descriptor.provider();
						return provider.attachVirtualMachine(descriptor);
					} catch (IOException ex) {
						virtualMachineFailureMap.put(descriptor, ex);
						logger.debug("Remote JVM descriptor found (attach-success, read-failure): " + label);
					} catch (AttachNotSupportedException ex) {
						virtualMachineFailureMap.put(descriptor, ex);
						logger.debug("Remote JVM descriptor found (attach-failure): " + label);
					} catch (Throwable t) {
						logger.error("Unhandled exception populating remote VM info", t);
					}
					return null;
				}).orTimeout(500, TimeUnit.MILLISECONDS).thenAccept(machine -> {
					// Get information from machine if it is available.
					if (machine != null) {
						virtualMachineMap.put(descriptor, machine);
						logger.debug("Remote JVM descriptor found (attach-success): " + label);

						// Extract additional information
						try {
							Properties systemProperties = machine.getSystemProperties();
							virtualMachinePropertiesMap.put(descriptor, systemProperties);
							virtualMachinePidMap.put(descriptor, pid);
							virtualMachineMainClassMap.put(descriptor, mapToMainClass(descriptor));

							// Enable optional JMX agent
							if (config.getAttachJmxAgent().getValue()) {
								try {
									Properties agentProperties = machine.getAgentProperties();
									String serviceUrl = agentProperties.getProperty(JMX_AGENT_ADDRESS);
									if (serviceUrl == null) {
										serviceUrl = machine.startLocalManagementAgent();
									}

									if (serviceUrl != null) {
										JMXServiceURL url = new JMXServiceURL(serviceUrl);
										@SuppressWarnings("resource") // Do NOT wrap this in a try-with-resource. It will close the connection.
										JMXConnector connector = JMXConnectorFactory.connect(url);
										MBeanServerConnection connection = connector.getMBeanServerConnection();
										virtualMachineJmxConnMap.put(descriptor, new JmxBeanServerConnection(connection));
									} else {
										logger.warn("Could fetch JMX agent address, skipping connection for: {}", label);
									}
								} catch (Exception ex) {
									logger.error("Failed to attach JMX agent to remote JVM: {}", label, ex);
								}
							}
						} catch (IOException ex) {
							logger.error("Could not read system properties from remote JVM: " + label, ex);
						}
					}

					// Add to list for listener call later.
					toAdd.add(descriptor);

					// Insert descriptor in sorted order.
					int lastComparison = 1;
					synchronized (virtualMachineDescriptors) {
						for (int i = 0; i < numDescriptors; i++) {
							VirtualMachineDescriptor other = virtualMachineDescriptors.get(i);
							int comparison = descriptorComparator.compare(descriptor, other);
							if (comparison < lastComparison) {
								virtualMachineDescriptors.add(i, descriptor);
								return;
							}
						}

						// Greater than all entries, append to end
						virtualMachineDescriptors.add(descriptor);
					}
				}));
			}
		}
		// When all attach attachFutures complete, update the observable list to update the UI
		ThreadUtil.allOf(attachFutures.toArray(new CompletableFuture[0])).thenRun(() -> {
			// Remove entries not visited in this pass
			virtualMachineDescriptors.removeAll(toRemove);
			for (VirtualMachineDescriptor descriptor : toRemove) {
				String label = descriptor.id() + " - " + StringUtil.withEmptyFallback(descriptor.displayName(), "?");
				logger.debug("Remote JVM descriptor removed: " + label);
			}

			// Call listeners
			Unchecked.checkedForEach(postScanListeners, listener -> listener.onScanCompleted(toAdd, toRemove),
					(listener, t) -> logger.error("Exception thrown after scan completion", t));
		});
	}
@return Path to agent jar file.
recaf-core/src/main/java/software/coley/recaf/services/attach/BasicAttachManager.java