Java不需要开发人员显式分配内存和回收内存,而是通过JVM来自动管理内存的分配和回收 ( 即GC )。这篇博文将用通俗易懂的方式来总结 JVM。
JVM的规范定义如下:
接下来我们分开来分析整个JVM。
1.JVM的内存管理:
如上图所示,Sun JDK 遵照JVM的规范,将内存划分为五部分,分别为:pc寄存器、JVM方法栈、本地方法栈、JVM方法区、JVM堆。
pc寄存器和JVM方法栈:
每个线程均会创建pc寄存器和JVM方法栈
pc寄存器占用的可能为cpu寄存器或者操作系统内存
JVM方法栈占用的是操作系统内存,JVM方法栈是线程私有
当方法运行完,其占用这两部分内存会自动释放。
本地方法栈:
本地方法栈用于支持本地方法的执行
存储了每个本地方法调用的状态
在Sun JDK 中,本地方法栈和JVM方法栈是同一个
JVM方法区:
存放了要加载的类的信息、类中的静态变量、类中定义的final类型的常量、类中的方法信息等。
方法区域是全局共享的
在Sun JDK 中方法区又称为持久代,默认最小值为16M,最大值为64M.
在一定条件下可以被GC.
JVM堆:
堆主要用于存储对象实例和数组值,Java中所有通过new创建的对象的内存都在堆中分配。由GC进行回收。
补充:老年代主要用来存放新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象,新建的对象也有可能在老年代上分配内存(大对象或大的数组对象,且数组要保证无引用外部对象)。
关于堆的细节内容,请看我之前写的一篇博文:
关于内存的分配问题:
Java对象所占有的内存主要是从堆上进行分配,堆是线程共享的,因此在堆上分配内存时需要加锁。当堆上空间不足时,会触发GC,若GC后空间仍然不足,则会抛出异常。
接下来,我们重点分析内存的回收:
JVM通过GC来回收堆和方法区中的内存
GC的基本原理是:
(1)先找到程序中不再被使用的对象
(2)回收这些对象所占用的内存
通常采用收集器的方式实现GC,主要的收集器有引用计数收集器和跟踪收集器。
1.引用计数收集器(分散式管理 ):
给每个对象设置一个引用计数器,当该对象被引用的时候,计数器的值加一,当计数器的值为0的时候,表示该对象没有被引用,即可以进行回收。
缺点:
(1)引用计数器需要在每次对象赋值时进行计数器的增减,有一定的消耗。
(2)引用计数器对于循环引用的场景没有办法进行回收。
所以,Java不使用这种收集器。
2.跟踪收集器 (集中式管理):
跟踪式收集器基于一定的条件触发,执行时需要从根集合扫描对象的引用关系,这会造成程序的暂停。
三种跟踪收集算法的缺点:
复制:成本是要增加一块新的内存以及进行对象的移动
标记--清除:会造成内存碎片
标记--压缩:不产生内存碎片但是对象移动成本高