Sí, cada subproceso tiene su propia pila. Esa es una necesidad difícil, la pila realiza un seguimiento de dónde regresa un método una vez que termina, almacena la dirección de devolución. Como cada subproceso ejecuta su propio código, necesitan su propia pila. Las variables locales y los argumentos del método se almacenan allí también, por lo que (normalmente) son seguros para subprocesos.
El número de montones es un detalle más complicado. Está contando 1 para el montón recogido de basura. Eso no es del todo correcto desde el punto de vista de la implementación, los tres montones generacionales más el Montículo de objetos grandes son montones lógicamente distintos, y suman hasta cuatro. Este detalle de implementación comienza a importar cuando asigna demasiado.
Otro que no puede ignorar por completo en el código administrado es el montón que almacena las variables estáticas. Está asociado con AppDomain, variables estáticas en vivo mientras viva el AppDomain. Comúnmente llamado "montón de cargador" en la literatura de .NET. En realidad, consta de 3 montones (alta frecuencia, baja frecuencia y pila de stub), el código jit y los datos de tipo también se almacenan allí, pero eso está llegando al fondo.
Más abajo en la lista de ignorar están los montones utilizados por el código nativo. Dos de ellos son fácilmente visibles desde la clase Marshal. Hay un montón de proceso predeterminado, Windows asigna desde allí, también lo hace Marshal.AllocHGlobal(). Y hay un montón separado donde COM almacena datos, Marshal.AllocCoTaskMem() los asigna. Por último, cualquier código nativo con el que interactúes tendrá su propio montón para su soporte en tiempo de ejecución. El número de montones utilizados por ese tipo de código está limitado solo por la cantidad de archivos DLL nativos que se cargan en su proceso. Todos estos montones existen, casi nunca lidias con ellos directamente.
Por lo tanto, 10 montones mínimo.
Gracias por el excelente enlace, muy buen recurso para tener. – Richard