Para o objetivo que você demonstrou, acredito que o uso do Java Agent seja uma opção.
Conforme este link, dependendo da implementação da JVM, é possível iniciar o Java Agent durante a execução. Porém, uma opção melhor, dentro do mesmo link, seria usar o MANIFEST.
O problema é que alguém poderia alterar esse MANIFEST. Aí eu acredito que existam opções para evitar isso. Eu já vi jars com assinaturas que verificavam o próprio conteúdo, então se o MANIFEST não estivesse correto ele não funcionaria, mas não sei qual o mecanismo adequado para fazer essas verificações. A documentação sobre assinaturas de MANIFEST está aqui e também tem este outro link interessante.
Uma outra opção, se o público alvo usasse Windows, seria usar um wrapper para executável como o http://launch4j.sourceforge.net/, pois então seria possível passar o argumento do Java Agent para a JVM sem que o mesmo fosse visível para o usuário.
Com o Java Agent em ação e com uma instância da classe Instrumentation, acho que só precisa chamar o método getAllLoadedClasses() para obter a lista de classes.
Um exemplo de implementação simples está neste outro link. A partir disso é só construir a lógica do hash.
Sobre a questão do validador validar a si mesmo, até é possível, desde que ele não tenha atributos que mudam, como listas ou mapas. Como eu havia anteriormente pensado em armazenar a lista de classes observadas numa lista, fiz uma suposição de que o conteúdo dessa lista poderia estar diferente no momento em que a classe verificasse a si mesma, logo ela seria um problema. Bem, ignore essa parte por enquanto.
Eu sei que pode ser chover no molhado, mas toda essa verificação ainda não garante muita coisa. Alguém poderia usar um descompilador (mesmo com código ofuscado), analisar a lógica de validação e usar um editor de bytecode para inibir o método de validação. Eu mesmo já usei essas ferramentas e posso dizer que não é tão impossível para um usuário avançado, basta um tanto de esforço.
Por outro lado, mesmo não sendo especialista em segurança ou em proteção de software, creio que o esforço despendido para inibir usuários mal intencionados será inversamente proporcional à quantidade de “quebras” de segurança, então, ainda que não se possa garantir 100%, cada esforço adicionará uma camada de segurança e tornará o benefício da quebra de segurança menos atraente para um “cracker”.