Geração de Números Aleatórios Muito Grande

Estou gerando uma matriz muito grande de números aleatórios, a matriz tem o tamanho de 20.000X20.000 (400 Milhões de posições) como a matriz é muito grande precisei aumentar o tamanho da heap da JVM (para evitar o erro Exception in thread “main” java.lang.OutOfMemoryError: Java heap space) compilando usando o seguinte comando:
java -jar -Xms0m -Xmx4G Teste.jar

Até aí ok, quando eu uso o range de números aleatórios de 0 à 10, o programa executa tranquilo, mas quando eu aumento o range para 0 à 100.000.000 eu recebo o erro Java heap espace novamente.

Gostaria de entender o porque isso acontece, já que o tamanho de um inteiro é sempre 4bytes, independente de que valor seja esse inteiro, não é ?? Logo não deveria faltar espaço para a JVM apenas porque aumentei o range dos números aleatórios mesmo mantendo o tamanho da matriz.

Segue o código:
Teste.java:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package teste;

import java.util.ArrayList;
import java.util.Random;

public class Teste {
    
    public static int TAM = 20000;
    private static final int rangeNumerosAleatorios = 100000000;//11;
    public static ArrayList<ArrayList<Integer>> matrizDeNumeros;

    public static void main(String[] args) {

        System.out.println(Thread.currentThread().getName());
        preencherMatriz();
        System.out.println("Matriz preenchida");
        //imprimirMatriz();
    }
    
    
    public static void preencherMatriz() {
        Random numero = new Random(2); // inicia semente para sempre gerar a mesma matriz
        ArrayList<Integer> linha;

        matrizDeNumeros = new ArrayList<ArrayList<Integer>>(TAM);
        for (int i = 0; i < TAM; i++) {
            linha = new ArrayList<Integer>();

            for (int j = 0; j < TAM; j++)
                linha.add(numero.nextInt(rangeNumerosAleatorios));

            matrizDeNumeros.add(linha); // adiciona uma linha a cada posição, formando uma matriz
        }
    }
    
    public static void imprimirMatriz() {
        for (int i = 0; i < matrizDeNumeros.size(); i++) {
            for (int j = 0; j < matrizDeNumeros.get(i).size(); j++)
                System.out.print(matrizDeNumeros.get(i).get(j) + " ");

            System.out.println("");
        }
    }
}

Saída quando executo com TAM = 20000 e rangeNumerosAleatorios = 100000000;

Você está confundindo tipos primitivos com objetos. Tanto ArrayList como Integer tem alguns custos extras de memória que vão além dos dados puros neles armazenados (cabeçalho dos objetos, tamanho da lista, etc). Um profiler vai te dar uma visão mais clara dos valores, mas por exemplo Integers podem chegar a 128 bits.

Outra questão é que ArrayList cria listas de tamanho pré-definido, quando este tamanho é superado, a VM reserva um novo espaço contíguo na memória e copia todos os elementos para esse novo espaço. Deixar ArrayList gerenciar seu tamanho sozinho, especialmente em listas tão longas, não é uma boa idéia. Como você já sabe o tamanho, eu aconselho a criar a lista com o tamanho desejado em todos os locais, incluindo aqui:

linha = new ArrayList<Integer>();
2 curtidas