自动内存管理机制
Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范 第2版》规定,运行时数据区包括:
1、程序计数器
一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它。当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令。每条线程都有一个独立的程序计数器,属于线程私有的内存。该区域是唯一一个没有规定任何OutOfMemoryError(内存溢出)情况的区域。
2、Java虚拟机栈
描述Java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。生命周期与线程相同,也属于线程私有的内存。
根据规范该区域存在两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,如果虚拟机栈进行动态扩展时无法申请到足够的内存时会抛出OutOfMemoryError(内存溢出)异常。
3、本地方法栈
为虚拟机使用的native方法服务。Java类的祖先类Object中有众多Native方法,如hashCode()、wait()等,他们的执行很多时候是借助于操作系统,但是JVM需要对他们做一些规范,来处理他们的执行过程。有的虚拟机(如:Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。也会抛出StackOverflowError和OutOfMemoryError异常。属于线程私有的内存。
4、堆
被所有线程共享的一块内存区域,在虚拟机启动时创建。几乎所有的对象实例以及数组都要在堆上分配内存。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。 Java堆是垃圾收集器管理的主要区域,也被称为“GC堆”,在32位系统上最大为2G,64位系统上无限制。可通过-Xms和-Xmx控制。Java性能的优化,主要就是针对这部分内存的。
如果堆中没有内存完成实例分配,并且堆也无法再进行扩展时,将会抛出OutOfMemoryError异常。
由于现在垃圾收集器基本采用分代收集算法,所以Java堆细分为:年轻代(Eden区、From Survivor区、To Survivor区) 和 年老代。Java堆上还可能划分出线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)
- 年轻代(New):年轻代用来存放JVM刚分配的Java对象
- 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
- Eden:Eden用来存放JVM刚分配的对象
- Survivor:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时(对象仍然存活),会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到年老代。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
5、方法区(非堆、”永久代“)
线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。垃圾收集器在这个区域是比较少出现的,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。可通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
运行时常量池
方法区的一部分,class文件的常量池用于存放编译期生成的各种字面值和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。当常量池无法满足内存分配需求时,将抛出OutOfMemoryError异常。
6、直接内存
在JDK1.4中新加入类NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,即我们所说的直接内存,这样在某些场景中会提高程序的性能。直接内存不是虚拟机运行时数据区的一部分(当然也不会受到Java堆大小的限制),也不是规范规定的,但这部分内存会被频繁使用,也可能抛出OutOfMemoryError异常。
垃圾收集(GC)
Java语言对程序员做了一个美好的承诺:程序员无需去管理内存,因为JVM有垃圾回收(GC),会去自动进行垃圾回收。其实不然:
- 垃圾回收并不会按照程序员的要求,随时进行GC。
- 垃圾回收并不会及时的清理内存,尽管有时程序需要额外的内存。
- 程序员不能对垃圾回收进行控制。
因为上面这些事实,以致我们在写程序的时候,只能根据垃圾回收的规律,合理安排内存,这就要求我们必须彻底了解JVM的内存管理机制,这样才能随心所欲,将程序控制于鼓掌之中。
GC需要完成的三件事:
- 哪些内存需要回收?
- 什么时候回收?
- 如何回收?(垃圾收集算法)
1、哪些内存需要回收?
在上面介绍的五大区中,有三个是不需要进行垃圾回收的:程序计数器、JVM栈、本地方法栈。因为它们的生命周期是和线程同步的,随着线程的销毁,它们占用的内存会自动释放,所以只有方法区和堆需要进行GC。
GC应该回收这样一些对象,这些对象没有任何引用指向(即对象已死)。 Java使用根搜索算法判断对象是否存活。基本思路是:通过一系列的名为“GC Roots”的对象作为起始点,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
Java语言里,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI(Native方法)的引用的对象。
四种引用的GC特点
JDK1.2之后,对引用进行了扩充,引入了强、软、若、虚四种引用,被标记为这四种引用的对象,在GC时分别有不同的意义:
- 强引用(Strong Reference)---就是为刚被new出来的对象所加的引用,它的特点就是,永远不会被回收。
- 软引用(Soft Reference)---声明为软引用的类,是可被回收的对象,如果JVM内存并不紧张,这类对象可以不被回收,如果内存紧张,则会被回收。此处有一个问题,既然被引用为软引用的对象可以回收,为什么不去回收呢?其实我们知道,Java中是存在缓存机制的,就拿字面量缓存来说,有些时候,缓存的对象就是当前可有可无的,只是留在内存中如果还有需要,则不需要重新分配内存即可使用,因此,这些对象即可被引用为软引用,方便使用,提高程序性能。
- 弱引用(Weak Reference)---弱引用的对象就是一定需要进行垃圾回收的,不管内存是否紧张,当进行GC时,标记为弱引用的对象一定会被清理回收。
- 虚引用(Phantom Reference)---虚引用弱的可以忽略不计,JVM完全不会在乎虚引用,其唯一作用就是做一些跟踪记录,辅助finalize函数的使用。
2、什么时候回收?
- 当年轻代内存满时,会引发一次普通GC(Minor GC),该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor满不会引发GC
- 当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代
- 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
两种GC的区别:
何时会抛出OutOfMemoryException?
并不是内存被耗空的时候才抛出,满足如下两个条件将触发OutOfMemoryException:
- JVM98%的时间都花费在内存回收
- 每次回收的内存小于2%
3、如何回收?(垃圾收集算法)
常见的GC算法:
1)标记-清除算法(Mark-Sweep)
最基础的GC算法,将需要进行回收的对象做标记,之后扫描,有标记的进行回收,这样就产生两个步骤:标记和清除。这个算法效率不高,而且在清理完成后会产生内存碎片,这样,如果有大对象需要连续的内存空间时,还需要进行碎片整理,所以,此算法需要改进。
2)复制算法(Copying)
前面我们谈过,新生代内存分为了三份,Eden区和2块Survivor区,一般Sun的JVM会将Eden区和Survivor区的比例调为8:1,保证有一块Survivor区是空闲的,这样,在垃圾回收的时候,将不需要进行回收的对象放在空闲的Survivor区,然后将Eden区和第一块Survivor区进行完全清理,这样有一个问题,就是如果第二块Survivor区的空间不够大怎么办?这个时候,就需要当Survivor区不够用的时候,暂时借持久代的内存用一下。此算法适用于新生代。
3)标记-整理(或叫压缩)算法(Mark-Compact)
和标记-清楚算法前半段一样,只是在标记了不需要进行回收的对象后,将标记过的对象移动到一起,使得内存连续,这样,只要将标记边界以外的内存清理就行了。此算法适用于持久代。
4)分代收集算法
根据各个年代的特点采用最适当的收集算法。
常见的垃圾收集器:
Java堆内存分配策略
对象优先在Eden区分配
大对象直接进入年老代
长期存活的对象将进入年老代
动态对象年龄判断
空间分配担保
---Java和C++之间有一堵有内存分配和垃圾回收技术围成的墙,墙外的人想进去,墙里的人想出去!
C、C++程序员有时苦于内存泄露,内存管理是件令人头痛的事儿,但是Java程序员呢,又羡慕C++程序员,自己可以控制一切,这样就不会在内存管理方面显得束手无策。