JVM内存区域
程序计数器(Program Counter Register)
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,即:线程私有。
虚拟机栈(Virtual Machine Stacks)
java虚拟机栈也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时,会创建一个栈桢,用于存储局部变量、操作数栈、动态链接和方法出入口等信息。即:虚拟机栈是针对于java方法执行过程中所涉及到的内存区域。
本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈所发挥的作用相似,区别在于,虚拟机栈为虚拟机执行JAVA方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
JAVA堆(Java Heap)
Java堆是虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建,此内存区域的唯一目的是存放对象实例。即:所有的对象实例以及数组,都要在堆上分配内存。Java堆按照不同的角度还可以分为:新生代、老年代或者 Eden空间区域、From Survivor空间、To Survivor空间。
方法区(Method Area)
方法区是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据。
运行时常量池(RunTime Constant Pool)
方法区的一部分,编译器生成的各种字面量和符号引用,在类加载后进入方法区的运行时常量池中。
直接内存(Direct Memory)
它并不是虚拟机运行时数据区的一部分,但这部分内存会被频繁的使用,也可能会导致OutOfMemoryError异常。在进行IO操作时,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免在Java堆和Native堆中来回复制数据。
内存溢出异常
Java堆溢出。异常代码形如:OutOfMemoryError:Java heap space。
不停的创建对象,而GC Roots 到这些对象之间有可达路径,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
可能原因:如果是内存泄漏,泄露的对象与GC Roots相关联,导致垃圾收集器无法回收,通过工具查看泄露对象到GC Roots 的引用链,掌握泄露对象信息和引用链信息,就能够找到引发内存泄露的代码位置。如果不是内存泄露,那说明对象依旧存活,因为无法回收,积累到一定量之后会引发内存溢出,可以通过调整虚拟机的堆参数(-Xmx和-Xms),或者从代码的角度检查是否有对象生命周期过长,持有状态时间过长等问题。虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常。
如果虚拟机在扩展栈深度是无法申请到足够的内存空间,则抛出OutOfMemoryError异常。方法区和运行时常量池溢出,异常代码形如:OutOfMemoryError:PermGen Space
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述和方法描述等。所以在使用反射、动态代理和CGlib等技术,运行时生成大量的动态类时,可能会出现内存溢出现象。本机直接内存溢出
DirectMemory容量可以通过 -XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值一样,因为直接内存并不是虚拟机的内存区域,所以当虚拟机内存区域+直接内存所使用的区域大于本机内存时,无法申请到本机内存,则抛出异常,除OutOfMemoryError字样外,无其余明显报错信息。
垃圾收集算法
标记-清除 算法
该算法分为“标记”和“清除”两个阶段,首先,标记所有需要回收的对象,在标记完成后,同意回收所有被标记的对象。
缺点:
1. 标记和清除两个过程效率都不高。
2. 标记清除后会产生大量不连续的内存碎片。
复制算法
将可用的内存按照容量划分为大小相等的两块,每次只使用一块,再回收的时候,将还存货的对象,复制到另外一块上面,然后,再把已使用的内存空间一次性清理掉。简单,高效。
缺点:可用内存区域变为原来的一半。对象存活率较高时,会进行较多的复制操作,效率降低(在Eden区域中较为合适,不宜在老年代中使用该算法)。
优化:因为大部分的对象都是“朝生夕死”,所以,并不需要按照1:1来划分内存区域,按照8:1来划分区域比较合理,即:Eden区域占80%,From Survivor区域占10%,To Survivor区域占10%。
标记-整理算法
标记整理算法的过程与标记清除算法相似,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象直接都想一端移动,然后直接清理掉端边界意外的内存。
分代收集算法
分代收集算法并不是一种新的算法,可以理解为一种思想,即:根据对象存活周期的不同,划分为几块(新生代和老年代)。新生代中每次垃圾收集,都有大批的对象死去,只有少量存活,适宜选用复制算法,而老年代中因为对象的存活率较高,所以使用标记-清除或者标记-整理算法来进行回收。