整体上来看,JVM的内存分为 堆区 和 非堆区 ,而非堆区又包括了 方法区、JVM栈、本地方法栈、程序计数器 等。
其主要作用是用于为几乎所有的 对象实例 和 数组实例 的实例化提供内存空间。说通俗点,所有采用 new关键字 产生的对象的空间都在此分配。 如: Map<String,String> map = new HashMap<>();
这个map所引用的对象即位于JVM的堆中。 JVM heap的特点是:
JVM栈是伴随着线程的产生而产生的,属于线程私有区域,生命周期和线程保持一致,所以,JVM的内存管理器对这部分内存无需管理; 从图1.1中可以看出,栈中有包含了多个 栈帧(frame) ,frame 伴随的方法的调用而产生 ,方法调用完毕,frame也就出栈,从JVM栈中被移除。frame主要保存方法的 局部变量、操作数栈、动态链接、方法出口(reternAddress或者抛Exception) 。 虚拟机规范中,对此内存区域规定了两种异常情况:
StackOverflowError OutofMemoryError
简称PC(Program Counter Register),为线程私有,如果执行的是一个Java方法,那么此时PC里保存的是 当前Java字节码的指令地址 ;如果说执行的是Native方法,那么PC的内容则为空。
主要用于存储已经由JVM加载的 类信息、常量、静态变量、即时编译器编译后的代码 等数据。在Hotspot JVM中,设计者使用“永久代”来实现方法区,这样带来的好处是,JVM可以不用再特别的写代码来管理方法区的内存,而可以像管理 JVM 堆一样来管理。带来的弊端在于,这很容易造成内存溢出的问题。我们知道,JVM的永久代使用 --XX:MAXPermSize来设定永久代的内存上限。 值得一提的是,运行时常量池也属于方法区的一部分,所以,它的大小是受到方法区的限制的。运行期间也可以将新的常量放入池中,运用的比较多的就是String的intern()方法。 注意:
与JVM栈非常类似,只不过,本地方法栈是为 Java的Native method方法 调用的时候服务。JVM规范并未定义如何实现该区域,但是,在Hotspot JVM中,本地方法栈和JVM栈合二为一,所以,本地方法栈同样会抛出 StackOverflowError
和 OutofMemoryError
。
垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收 无任何对象引用的对象占据的内存空间 的一种机制。
垃圾收集器会对堆进行回收前,判断对象中哪些是“存活”,哪些是“死亡”。
每当一个地方引用它时,计数器+1;引用失效时,计数器-1;计数值=0 —— 不可能再被引用。 缺点:对象之间相互矛盾 循环引用 的问题。
把一系列“GC Roots”作为起始点,从节点向下搜索,路径称为 引用链 ,当一个对象 到GC Roots没有任何引用链相连 ,即不可达时,则证明此对象时不可用的。
标记-清除(Mark-Sweep)算法是现代垃圾回收算法的思想基础。标记-清除算法将垃圾回收分为两个阶段: 标记阶段 和 清除阶段 。一种可行的实现是,在标记阶段,首先通过根节点, 标记所有从根节点开始的可达对象 。,未被标记的对象就是未被引用的垃圾对象(好多资料说标记出要回收的对象,其实明白大概意思就可以了)。然后,在清除阶段, 清除所有未被标记的对象 。
标记整理算法类似与标记清除算法,不过它标记完对象后,不是直接对可回收对象进行清理,而是让所有存活的对象都 向一端移动 ,然后直接 清理掉边界以外的内存 。
复制算法可以解决效率问题,它将可用内存按容量划分为 大小相等 的两块,每次只使用其中的一块,当这一块内存用完了,就 将还存活着的对象复制到另一块上面 ,然后 再把已经使用过的内存空间一次清理掉 ,这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可(还可使用TLAB进行高效分配内存)。
在JVM的内存空间中把堆空间分为 年老代 和 年轻代 。将大量(据说是90%以上) 创建了没多久就会消亡的对象 存储在年轻代,而年老代中存放 生命周期长久的实例对象 。年轻代中又被分为Eden区(圣经中的伊甸园)、和两个Survivor区。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区。