站在垃圾收集器的角度来看, 可以把内存分为新生代与老年代. 内存的分配规则取决于当前使用的是哪种垃圾收集器的组合, 以及内存相关的参数配置. 往大的方向说, 对象优先分配在新生代的 Eden 区域, 而大对象直接进入老年代.
第一, 新生代的 Eden 区域, 对象优先分配在该区域, 同时 JVM 可以为每个线程分配一个私有的缓存区域, 称为 TLAB(Thread Local Allocation Buffer), 避免多线程同时分配内存时需要使用加锁等机制而影响分配速度. TLAB 在堆上分配, 位于 Eden 中.
第二, 新生代的 Survivor 区域. 当 Eden 区域内存不足时会触发 Minor GC, 也称为新生代 GC, 在 Minor GC 存活下来的对象, 会被复制到 Survivor 区域中. 我认为 Survivor 区的作用在于避免过早触发 Full GC. 如果没有 Survivor,Eden 区每进行一次 Minor GC 都把对象直接送到老年代, 老年代很快便会内存不足引发 Full GC. 新生代中有两个 Survivor 区, 我认为两个 Survivor 的作用在于提高性能, 避免内存碎片的出现. 在任何时候, 总有一个 Survivor 是 empty 的, 在发生 Minor GC 时, 会将 Eden 及另一个的 Survivor 的存活对象拷贝到该 empty Survivor 中, 从而避免内存碎片的产生.
第三, 老年代. 老年代放置长生命周期的对象, 通常是从 Survivor 区域拷贝过来的对象, 不过当对象过大的时候, 无法在新生代中用连续内存的存放, 那么这个大对象就会被直接分配在老年代上. 一般来说, 普通的对象都是分配在 TLAB 上, 较大的对象, 直接分配在 Eden 区上的其他内存区域, 而过大的对象, 直接分配在老年代上.
第四, 永久代. 如前面所说, 在早起的 Hotspot JVM 中有老年代的概念, 老年代用于存储 Java 类的元数据, 常量池, Intern 字符串等. 在 JDK8 之后, 就将老年代移除, 而引入元数据区的概念.
第五, Vritual 空间. 前面说过, 可以使用 Xms 与 Xmx 来指定堆的最小与最大空间. 如果 Xms 小于 Xmx, 堆的大小不会直接扩展到上限, 而是留着一部分等待内存需求不断增长时, 再分配给新生代. Vritual 空间便是这部分保留的内存区域.
Java 堆内的内存结构大体为:
来源: http://www.bubuko.com/infodetail-3296335.html