Bom, vamos lá.
Na verdade, ambos tipos de variáveis que você comentou recebem endereços de memória. Quando você declara uma variável, o programa solicita ao Sistema Operacional que seja reservado um espaço na memória cujo tamanho vai depender do seu tipo, e em troca o SO devolve o endereço que corresponde a esse espaço. Quando a variável é inicializada/atribuída, aquele espaço na memória reservada pelo SO é preenchido com o valor correspondente.
Por exemplo.
Vamos supor que o seu programa tem a linha abaixo:
int x;
Ao localizar essa linha, o SO vai procurar um espaço livre na memória equivalente a 4 bytes, ou 32 bits se preferir, e se encontrar vai devolver o endereço que corresponda a esse espaço. Vamos supor que o SO devolveu o número "1000" que identifique esse espaço. Logo, esse espaço está reservado para o seu programa, especificamente para a variável nomeada como "x". Quando essa variável receber um valor, o seu programa simplesmente vai solicitar ao SO, através do endereço "1000" para que aloque o conteúdo recebido.
Quando estamos falando de objeto, é tudo igual ao que citei acima. Ou seja, quando você declara algo como:
Meal y;
O programa vai solicitar ao SO um espaço de memória equivalente a 4 bytes (ou 8, dependendo da plataforma) e este irá devolver o endereço e assim por diante. O que muda, é que neste espaço de 4 bytes (ou 8 bytes), em vez de ser atribuído um "objeto" (cujo tamanho em bytes pode variar muito, dependendo de quantos membros ele tenha etc.), é atribuído o endereço no qual esse objeto existe.
Para exemplificar, veja no exemplo do "int". Suponha que exista a linha de código abaixo:
int x = 1;
Quando essa linha for executada, lá no endereço 1000 do nosso exemplo, vamos encontrar o valor abaixo (número 1 em binário, ocupando o espaço de 32 bits ou 4 bytes):
00000000000000000000000000000001
Já quando a linha abaixo é executada, em vez de ser o objeto em si, é armazenado o endereço onde esse objeto existe.
Meal y = new Meal();
Lá no endereço da variável "y", vamos encontrar algo como:
00000000000000000000000001010011
Que é o endereço onde o seu programa deve solicitar ao SO para que seja encontrado o objeto propriamente dito.
Portanto, quando você cria um objeto com o uso do "new" e o atribui a uma variável, na verdade esse objeto está sendo criado em um endereço qualquer que o SO encontrar na hora para ele, e este endereço é armazenado em sua variável.
Quando você passa um argumento para um método, na linguagem Java, você está passando portanto uma "cópia" do seu conteúdo.
Veja o exemplo abaixo:
public static void main(String[] args) {
int x = 1;
System.out.println(x); //Vai imprimir 1
soma(x);
System.out.println(x); //Vai imprimir 1 de novo
}
static void soma(int y) {
y = y + 1;
}
Veja que vai imprimir 1 duas vezes. Isso porque o Java vai enviar, como eu falei, uma cópia do conteúdo de "x" para o método "soma". Portanto, a variável x declarada lá no main não vai sofrer alteração, pois apenas foi enviada uma cópia do seu conteúdo.
Veja esse outro exemplo:
public static void main(String[] args) {
Meal x = new Meal();
x.name = "a";
System.out.println(x.name); //Vai imprimir "a"
altera(x);
System.out.println(x.name); //Vai imprimir "b"
}
static void altera(Meal y) {
y.name = "b";
}
Veja que a propriedade "name" foi alterada, mas se a variável apontando para um objeto funciona da mesma forma, então porque houve essa alteração? É porque a variável nesse caso recebe um endereço de onde o objeto está?
Portanto, a variável "y" do método altera recebe uma cópia do conteúdo da variável "x", ou seja, recebe uma cópia do endereço de onde o objeto do tipo Meal foi criado anteriormente. Logo, ambas as variáveis podem alterar o mesmo objeto, pois ambas possuem o mesmo endereço. Agora, veja o último exemplo abaixo:
public static void main(String[] args) {
Meal x = new Meal();
x.name = "a";
System.out.println(x.name); //Vai imprimir "a"
altera(x);
System.out.println(x.name); //Vai imprimir "a" de novo!
}
static void altera(Meal y) {
y = new Meal()
y.name = "b";
}
Veja que nesse caso, a variável "y" teve uma nova atribuição, recebendo nesse caso um novo endereço de um outro objeto do tipo Meal. Como eu falei, o Java passa "cópias" das variáveis como argumento, portanto, a nova atribuição dentro do método não altera a variável x.
Quando o método "altera" terminar de ser executado, a variável "y" é descartada, pois o escopo dela se encerra. Assim como a variável "x" é descartada quando o método main se encerra. E os dois objetos criados no exemplo anterior serão removidos pelo Garbage Collector quando ele decidir que ambos estão elegíveis para serem removidos.
Bom, isso tudo é bem complexo mesmo Erica, então não se sinta um peixe fora d'água =)