1,java中内存区域划分
Java虚拟机在执行Java程序的过程中,会把它所管理的内存划分为若干不同的数据区域,包括:
a.栈(虚拟机栈VM Stack ,本地方法栈Native Method Stack)
b.堆 (Heap)
c.方法区(Method Area)
d.程序计数器(Program Counter Register)
其中:a,c 为所有线程共享的数据区;b,d 为线程隔离的数据区;
下面我们分别介绍一下各个区域
Java虚拟机栈
Java虚拟机栈是线程私有的,生命周期与线程相同。虚拟栈描述的是Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧,用户存储局部变量表,操作数栈,动态链接,方法出口等信息。
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
本地方法栈
本地方法栈为虚拟机使用到的Native方法服务。
Java堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
方法区
方法区是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器后的代码等数据。
内存回收目标主要是针对常量池的回收和对类型的卸载。
运行时常量池
运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。
程序计数器
程序计数器(Program Counter Register)是一块较小的内存区域,它可以看作是当前线程所执行的字节码的数字信号指示器。
每条线程都需要一个独立的程序计数器,是为了线程切换后能恢复到正确的位置。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
程序计数器,虚拟机栈,本地方法栈3个区域随线程而生,随线程而灭。
栈中的栈帧随着方法的进入和退出而有条不紊的执行这出栈和入栈的操作。每一个栈帧中分配多少内存是在类结构确定下来时就已知的,这几个区域的内存分配和回收多具备确定性。这几个区域不需要过多的考虑回收的问题,方法结束或者线程结束时。内存就自然跟随着回收了。
Java堆和方法区,只有在程序运行期间才能知道会创建那些对象,内存的分配和回收都是动态的。
2,JVM在进行垃圾回收之前,需要判断那些对象是需要回收的
引用技术算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻的计数器为0的对象就是不可能再倍使用的。
弊端:很难解决对象之间相互循环引用的问题。
可达性分析算法
通过一系列的称为GC Roots 的对象作为起始点,从这些节点开始向下搜索。搜索所有走过的路径称为引用链,当一个对象到GC Roots对象没有任何引用链,则证明此对象是不可用的。
可作为GC Roots的对象包括下面几种:
1,虚拟机栈(栈帧中的本地变量表)中引用的对象
2,方法区中常量引用的对象
3,方法区中类静态属性应用的对象
4,本地方法中JNI引用的对象
回收方法区
方法区(永久代)的垃圾回收主要回收两部分:废弃常量和无用类。
无用类的判定条件:
1,该类所有的实例都已被回收。
2,加载该类的ClassLoader被回收。
3,该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
3,通过垃圾收集算法进行垃圾回收
标记清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
复制算法
将可用内存按容量划分为大小相等的两块,每次都是用其中一块。当这一块的内存用完了,就将还或者的对象复制到另一块上面,然后再把已使用的内存空间一次清理掉。
标记整理算法
标记过程仍然与标记清除算法一样,但是后需步骤不是直接对可回收的对象进行清理,而是让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
分代收集算法
根据对象生活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用适当的算法。
在新生代中,每次垃圾收集时都发现有大批的对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
在老年代中,因为对象存活率高,没有额外空间对它进行分配担保,就必须使用标记清理或者标记整理算法来实现。