A única maneira de descarregar uma classe é se o Classloader usado for coletado de lixo. Isso significa que as referências a todas as classes e ao próprio carregador de classes precisam seguir o caminho do dodo.
Uma solução possível para o seu problema é ter um Classloader para cada arquivo jar e um Classloader para cada um dos AppServers que delega o carregamento real de classes para carregadores de classes Jar específicos. Dessa forma, você pode apontar para diferentes versões do arquivo jar para cada servidor de aplicativos.
Isso não é trivial, no entanto. A plataforma OSGi se esforça para fazer exatamente isso, pois cada pacote configurável possui um carregador de classe diferente e as dependências são resolvidas pela plataforma. Talvez uma boa solução seria dar uma olhada nisso.
Se você não deseja usar o OSGI, uma implementação possível pode ser usar uma instância da classe JarClassloader para cada arquivo JAR.
E crie uma nova classe MultiClassloader que estenda o Classloader. Essa classe internamente teria uma matriz (ou Lista) de JarClassloaders e, no método defineClass (), iteraria por todos os classloaders internos até que uma definição fosse encontrada ou uma NoClassDefFoundException fosse lançada. Alguns métodos de acessador podem ser fornecidos para adicionar novos JarClassloaders à classe. Existem várias implementações possíveis na rede para um MultiClassLoader, portanto você pode nem precisar escrever sua própria.
Se você instancia um MultiClassloader para todas as conexões com o servidor, em princípio é possível que cada servidor use uma versão diferente da mesma classe.
Usei a ideia do MultiClassloader em um projeto, onde as classes que continham scripts definidos pelo usuário tinham que ser carregadas e descarregadas da memória e funcionavam muito bem.