Olha, na maioria das JVMs comerciais (todas, eu acho), cada objeto corresponde a uma área de memória. No offset 0 está a referência ao objeto Class correspondente (aquilo que é retornado pelo Object.getClass()), e os demais espaços da área de memória do objeto contém os atributos, um após o outro.
Existem quatro tipos de bytecodes de chamada de métodos: invokestatic, invokevirtual, invokeinterface e invokespecial. Há propostas para que no java 7 apareça o invokedynamic.
De uma forma simples*, podemos dizer que o invokestatic recebe como parâmetro um ponteiro para um objeto Class, um ID de um método estático e um monte de tipos primitivos e ponteiros de Objects que são os parâmetros. Então a JVM vai lá no objeto Class, procura o método ESTÁTICO correspondente ao ID e o invoca passando-lhe os parâmetros.
O invokevirtual e o invokeinterface recebem como parâmetro um ponteiro de um objeto que é a instância em que o método é invocado, um ID de um método não-estático e os parâmetros. A JVM verifica se o ponteiro da instância é nula e lança NullPointerException se for. Se não for, ela vai lá no endereço apontado pelo ponteiro da instância (que é onde está o objeto) e pega a classe deste (que está no offset 0, ou seja exatamente para onde o ponteiro aponta). Tendo então a classe, ela procura o método NÃO-ESTÁTICO correspondente ao ID e o invoca passando-lhe os parâmetros.
O invokespecial é para a chamada de construtores e se não me engano chamada de métodos sobrescritos da superclasse (não sei bem direito como isso funciona no nível dos bytecodes, mas isso não é relevante aqui neste tópico). O invokedynamic vamos deixar para quando sair o java 7.
Então, a estrutura do objeto não contém referências aos métodos. O objeto tem apenas um ponteiro para o objeto Class correspondente e os atributos um após o outro. A JVM usa o ponteiro para o objeto Class para localizar os métodos. Desta forma, objetos de uma mesma classe, ao apontar para o mesmo objeto Class usarão os mesmos métodos que estão no mesmo endereço de memória. Portanto, não há diferenças no uso da memória ou no desempenho** ao deixar um método como estático ou não-estático.
-
- Simples, bem simples, para leigos entenderem.
** - Nunca vi nenhum benchmark de quanto tempo a JVM demora para empilhar os parâmetros para a chamada do método (incluindo o ID do método e a referência da instância do invokevirtual/invokeinterface), e nem quanto tempo ela demora para verificar se a referência da instância é nula ou não. Mas seja lá qual for este tempo, ele deve ser ínfimo e desprezível. Se você está tão desesperado por desempenho que isto pode lhe fazer diferença, então você nem devia estar programando em java, e sim em assembler (ou até no hardware).
EDITs: Correções de pequenos erros.