sirve referencia que para metodos herencia comando java constructor java-8 out-of-memory method-reference

referencia - para que sirve el comando super en java



¿Horrible rendimiento y gran espacio de almacenamiento dinámico de referencia de constructor Java 8? (2)

Acabo de tener una experiencia bastante desagradable en nuestro entorno de producción, causando OutOfMemoryErrors: heapspace..

Rastreé el problema con mi uso de ArrayList::new en una función.

Para verificar que esto realmente está funcionando peor que la creación normal a través de un constructor declarado ( t -> new ArrayList<>() ), escribí el siguiente método pequeño:

public class TestMain { public static void main(String[] args) { boolean newMethod = false; Map<Integer,List<Integer>> map = new HashMap<>(); int index = 0; while(true){ if (newMethod) { map.computeIfAbsent(index, ArrayList::new).add(index); } else { map.computeIfAbsent(index, i->new ArrayList<>()).add(index); } if (index++ % 100 == 0) { System.out.println("Reached index "+index); } } } }

Ejecutando el método con newMethod=true; hará que el método falle con OutOfMemoryError justo después de que el índice OutOfMemoryError a 30k. Con newMethod=false; el programa no falla, pero sigue golpeando hasta que se mata (el índice alcanza fácilmente 1.5 millones).

¿Por qué ArrayList::new crea tantos elementos Object[] en el montón que causa OutOfMemoryError tan rápido?

(Por cierto, también ocurre cuando el tipo de colección es HashSet ).


En el primer caso ( ArrayList::new ) está utilizando el constructor que toma un argumento de capacidad inicial, en el segundo caso no lo está. Una gran capacidad inicial ( index en su código) hace que se asigne un gran Object[] , lo que da como resultado su OutOfMemoryError s.

Aquí están las implementaciones actuales de los dos constructores:

public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

Algo similar sucede en HashSet , excepto que la matriz no se asigna hasta que se llama a add .


La firma computeIfAbsent es la siguiente:

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

Entonces el mappingFunction es la función que recibe un argumento. En su caso, K = Integer y V = List<Integer> , por lo que la firma se convierte (omitiendo PECS):

Function<Integer, List<Integer>> mappingFunction

Cuando escribe ArrayList::new en el lugar donde es necesaria la Function<Integer, List<Integer>> , el compilador busca el constructor adecuado que es:

public ArrayList(int initialCapacity)

Así que esencialmente su código es equivalente a

map.computeIfAbsent(index, i->new ArrayList<>(i)).add(index);

Y sus claves se tratan como valores de initialCapacity que conducen a la preasignación de matrices de tamaño cada vez mayor, lo que, por supuesto, bastante rápido conduce a OutOfMemoryError .

En este caso particular, las referencias de constructor no son adecuadas. Use lambdas en su lugar. ¿Fue el Supplier<? extends V> Supplier<? extends V> usado en computeIfAbsent , entonces ArrayList::new sería apropiado.