Java虚拟机内存分配

Java内存

1.Java作为自动内存分配与回收的语言,了解内存分配的原理可以加深对JVM的理解,对于上层代码层面可以写出更健壮的代码。涉及到内存分配,必然要提及内存回收。程序的OOM显然是不合理的,理解清楚OOM的本质比解决OOM这个bug更重要。

2.Java内存分配,通常理解的是五大部分,但是需要明确的是,内存还是可以在细分的。作为不同的开发那主要的关注的点是不同的:
twqiKU.png

堆(Heap)

1.通常认为堆是线程共享的区域(严格意义上,HotSpot提供了TLAB一种优化策略,实际上它在内存的读取上是内存共享的,但是分配上确实独享的)。虚拟机启动时被创建,实例对象在堆上分配内存如:new newarray等,一般是动态创建出来的对象。创建出来的对象包含各自的成员变量,但是不包含成员方法,成员方法是同一个类对象共享的。堆还可以分为年轻代,年老代;年轻代还可以细分为一个Eden两个Survivor区,而这样区分的目的是为了更好的垃圾回收。

方法区(Method Area)

1.同样是线程共享区域,主要存储被加载过类的常量,静态变量等类的信息,按照分代的思想,方法区被视为永久代

程序计数器(Program Counter Register)

1.线程私有,一块较小的空间,当前线程字节码执行的指示器。字节码解释器通过对程序计数器的值来决定执行相应的代码逻辑,像分支,循环,异常处理等。

Java栈(Stack)

1.线程私有,生命周期与线程相同,当方法执行时会创建一个栈帧,存储局部的变量表、动态链接、方法出口等。方法的执行到完成的过程就是栈帧的出栈与入栈的过程。另外对于局部变量表存储了编译时期的基本数据类型(八种)和引用类型(reference),但是并不是代表对象本身,只是一个指向对象的引用指针,或是指向一个代表对象的句柄或其他与此对象相关的位置。

本地方法栈(Native Method Stack)

1.本地方法栈使用的是Native方法服务,与Java Stack的作用类似。

运行时常量池

1.作为方法区的一个部分,用于存放编译时期各种字面量与符号引用,是具有动态性的。

GC

1.前面提到,Java程序动态的创建对象,而对象实例是被放在堆内存当中。堆内存在虚拟机启动时分配的内存大小的固定,如果无休止的创建对象不加以回收显然堆内存是不够用的也就是常见的OOM异常。Java作为动态管理内存的语言,垃圾收集也是JVM来完成的,那么虚拟机肯定是需要哪些对象是不会在被使用的了,对“死亡对象”进行回收。
GCRoots

引用计数法

1.概念很好理解,为对象添加引用计数器,每当对象被调用(引用)计数器自增。引用被释放则自减。按照这种逻辑,当一个对象的引用计数器为0时则认为对象没有被任务地方引用了,即可以被回收了。如果深入理解发现,这种情况下是处理不了循环引用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ReferenceCount {

private Object reference = null;

public ReferenceCount() {

}

public static void main(String[] args) {
ReferenceCount objectA = new ReferenceCount();
ReferenceCount objectB = new ReferenceCount();

objectA.reference = objectB;
objectB.reference = objectA;

objectA = null;
objectB = null;
}
}

可达性分析算法

1.Java通过可达性分析算法来判断对象是否存活,通过规定GC Roots作为起点,从起点开始向下搜索整个引用链,当某个对象到达GC Roots没有任何一条引用时,则认为此对象没有任何引用了。这样是能够解决上述的循环引用的问题。那什么样的对象可以作为GC Roots对象呢?主要有四种:

栈帧中本地变量表中引用的对象
方法区中静态属性引用的对象
方法区中常量引用的对象
JNI中引用的对象

几种引用

1.即使采用可达性分析算法,判断对象是否存活还是不够智能。内存作为稀缺的资源,虽然某个对象的引用还在,可是这个对象可能都不会在被使用,那么当内存不足时应该被收集的。Java规定了四种常见的引用:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。强引用很常见,创建的对象ClassA a = new ClassA(),这类引用则为强引用,虚拟机对强引用是不会回收的,即使抛出OOM异常。软引用,当内存充足的时候不会回收软引用的对象,而当内存不足的时会回收。弱引用则不管内存是否充足,在下一次垃圾回收时会被回收掉。虚引用,形同虚设,无论何时都会被回收,只是在被回收时会收到一个系统的通知,需要配合引用队列使用。

标记-清除

1.标记-清除算法分为两个阶段,标记阶段与清除阶段。标记出所有需要清理的对象,然后统一回收被标记的对象,无论标记还是清除都是比较耗时的效率低下。同时也带来另一个问题,内存不连续造成大量的内存碎片,由于堆内存的大小是虚拟机启动时固定的,假设此时需要一块较大的内存存放数组数据。显然存在申请不到这样一块较大内存的风险,那么又会触发垃圾收集。虽然如此,标记-清除还是指导了对回收算法改进的作用。

标记-整理

1.标记整理算法基于标记清除算法,都用到了标记阶段,只是对被标记的对象不在进行清除,而是将它们移动到一端。最后直接清除端边界以外的内存,这样解决了标记清除算法造成内存不连续的不足,但效率同样比较低下。

复制算法

1.为了解决标记-清除的效率问题,实现的复制算法,主要思想将内存分为大小相等的两块,每次使用其中额一块内存,当这半块内存将用完时,将还存活的对象复制到另半块内存上。统一对死亡对象进行收集,效率提高,但是只用一半的内存发挥了最大作用。另一半只是相当于备份。由于堆内存的特性细分为年轻代、年老代。年轻代中的对象更新频繁,回收的也频繁,实际上推荐的复制算法并没有按照1:1来划分,而是按照Eden:Survivor = 8:1来分配的。另外由于Survivor是两块区域,每次使用Eden与其中一块Survivor区域,复制时将存活的对象拷贝到另一块Survivor上,最后收集Eden与Survior

分代收集算法

1.还是基于基本的算法,只是通过虚拟机各个区域不同的对象来采用不同的收集算法。对于堆中的年轻代使用复制算法,此区域垃圾回收频繁需要较高的效率支撑。而年老代对象存活时间久就适合标记-整理进行垃圾回收。

这个功能是摆设,看看就好~~~