Java内存管理机制
java虚拟机有一套自动内存管理机制,不需要为每一个new对象去写相应的delete方法,不容易出现内存泄漏和内存溢出的问题,但是在开发过程中如果不了解java虚拟机的内存管理的话一旦出现内存泄漏和内存溢出也很难进行排查。
一、java内存区域与内存溢出
java虚拟机在执行java程序的过程中把它所管理的内存分为若干个不同的数据区域
1.1、程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
线程私有的内存。
由于java虚拟机中多线程是通过轮流切换并分配处理器执行时间来实现的,在任何一个确定的时刻,一个处理器(多核处理器一个内核)都只会执行一个线程中的指令。因此,为了线程切换后能恢复到正确的位置,每个线程都需要一个独立的程序计数器
1.2、java虚拟机栈
java虚拟机栈描述的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量、操作栈数、动态链接、方法出口等信息。(方法执行到完成对应入栈到出栈)
虚拟机栈中的局部变表存放基础数据类型和对象的引用。
与程序计数器一样,java虚拟机栈也是线程私有的,他的生命周期与线程相同。
如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出StackOverflowError异常。现在java虚拟机一般都是可扩展的,如果扩展时无法申请到足够的内存,会抛出OutOfMemoryError。
1.3、本地方法栈
1.4、java堆
java堆是java虚拟机管理的内存中最大的一块。java堆是所有线程共享的一块区域,在虚拟机启动时创建,该区域的作用是存放对象实例,几乎所有的对象实例都在这里创建。
java堆是垃圾收集器管理的主要区域,由于现在收集器基本都是采用分代回收算法,所以java堆中还可以细分为新生代和老年代。
如果在堆中没有内存完成实例分配,并且堆也无法在扩展,将会抛出OOM。
1.5、方法区
各个线程共享,用于存储已被虚拟机加载的类信息,常量,静态变量以及及时编译器编译后的代码等数据。方法区还有个别名叫"非堆"。
二、垃圾回收
提到垃圾回收,一般说来,我们要解决一些三个问题:
- 哪些内存回收?
- 什么时候回收?
- 如何回收?
这些问题分别对应着引用管理和回收策略等方案。
2.1、判断对象是否死亡
- 引用计数算法
有对这个对象的引用就+1,不再引用就-1,任何时刻计数为0的对象就代表不再使用。
实现简单、效率高、但存在循环引用的问题 - 可达性分析算法
可达性分析算法通过一系列称为GC Roots的对象作为起始点,从这些节点从上向下搜索,搜索走过的路径称为引用链,当一个对象没有任何引用链 与GC Roots连接时就说明此对象不可用,也就是对象不可达。
GC Roots对象通常包括:
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中类的静态属性引用的对象
- 方法区中常量引用的对象
- Native方法引用的对象
可达性分析算法整个流程如下所示:
第一次标记:对象在经过可达性分析后发现没有与GC Roots有引用链,则进行第一次标记并进行一次筛选,筛选条件是:该对象是否有必要执行finalize()方法。没有覆盖finalize()方法或者finalize()方法已经被执行过都会被 认为没有必要执行。
如果有必要执行:则该对象会被放在一个F-Queue队列,并稍后在由虚拟机建立的低优先级Finalizer线程中触发该对象的finalize()方法,但不保证一定等待它执行结束,因为如果这个对象的finalize()方法发生了死循环或者执行 时间较长的情况,会阻塞F-Queue队列里的其他对象,影响GC。
第二次标记:GC对F-Queue队列里的对象进行第二次标记,如果在第二次标记时该对象又成功被引用,则会被移除即将回收的集合,否则会被回收。
2.2、引用类型
- 强引用:代码中普遍存在的,只要强引用还存在,垃圾收集器就不会回收掉被引用的对象。
- 软引用:SoftReference,用来描述还有用但是非必须的对象,当内存不足的时候回回收这类对象。
- 弱引用:WeakReference,用来描述非必须对象,弱引用的对象只能生存到下一次GC发生时,当GC发生时,无论内存是否足够,都会回收该对象。
- 虚引用:PhantomReference,一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用取得一个对象的引用,它存在的唯一目的是在这个对象被回收时可以收到一个系统通知。