JVM内存结构、GC算法
JVM内存结构

堆:线程共享。是Jvm中内存中最大的一块,在虚拟机启动时创建,所有的对象实例以及数组都要在堆上分配。GC主要管理的对象。
方法区:线程共享。储存类信息、常量、静态变量、即时编译器编译后的代码。
栈(虚拟机栈和本地方法栈):线程私有。生命周期也与线程相同。栈由一系列的帧组成,帧保存每一个方法的局部变量、操作数栈、常量池指针,每一次方法的调用创建一个帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法调用直至执行完毕的过程就对应一个帧在栈中的入栈到出栈的过程。
栈帧字节对齐
32位操作系统,栈帧对齐是4字节,32bit,栈帧内存大小是4字节的整数倍。
64位操作系统,栈帧对齐是8字节,64bit,栈帧内存大小是8字节的整数倍。
本地方法栈的功能特点类似于虚拟机栈,均具有线程隔离的特点和都能及时抛出StackOverFlowError和OOM异常,不同的是本地方法栈服务于JVM执行的Native方法,而虚拟机栈服务于JVM执行的Java方法。
jvm在栈中可能会抛出的两种异常:
1.如果线程请求的栈深度太深,超出了虚拟机所允许的深度,就会出现StackOverFlowError(比如无限递 归,因为每一层帧都占用一定空间,超出这个值就会报错)
2.虚拟机栈可以动态扩展,如果扩展到无法申请到足够的内存空间,就会报OOM。
程序计数器:线程私有。是一块较小的内存空间,线程所执行的字节码的行号指示器,指向下一条指令的地址。
两条黄金法则:
1.引用类型总是被分配到“堆”上。不论是成员变量还是局部
2.基础类型总是分配到它声明的地方:成员变量在堆内存里,局部变量在栈内存里。
对象分配规则:

我们知道对象都是分配在堆上,堆内存是由年轻代和老年代组成,年轻代又可以分为Eden空间、From Survivor空间、To Survivor空间,默认情况下按照8:1:1的比例来分配
对象会优先在堆的Eden区分配
大对象直接进入老年代
长期存活的对象将直接进入老年代
当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC
Minor GC通常发生在年轻代的Eden区上,在这个区的对象生存期短,往往发生GC的频率较高,回收的速度较快;Full GC/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是可以通过配置实现,可以在Full GC之前进行一次Minor GC,这样可以加快老年代的回收速度。
详细说明:

- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
- 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
- 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Minor GC,如果false则进行Full GC。
GC算法 垃圾回收
对象存活判断
对象存活判断一般有两种方式:
1.引用计数,每个对象有一个引用计数属性,新增一个引用时,计数加1,引用释放时计数减1,计数为0时,可以回收,此方法简单,但是无法决绝对象互相循环引用的问题。
2.可达性分析,从GC Roots开始向下搜索,搜索走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。可以回收
GC算法
GC最基础的算法有三种:标记-清除算法、复制算法、标记-压缩算法,我们通常用的GC一般是采用分代收集算法。
标记-清除算法:分为“标记”和“清除”两个阶段,首先标出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
复制算法:复制的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存使用完了,它就将还活着的对象复制到另一块上面,然后再把已经使用的内存空间清除掉。它浪费了一半的内存且复制所花费的时间不可忽略
标记压缩:和标记清除算法类似,但最后不是清除,而是让所有标记的对象都移向一端,然后直接清理掉端边界以外的内存。
分代收集算法:把Java内存分为新生代和老年代,这样就可以根据各年代的特点采用最适当的收集算法。
元空间

JDK8之前。Hotspot虚拟机的方法区实际上是有永久带的实现。在JDK8之后。Hotspot虚拟机不再使用永久代。而是采用了全新的元空间。类的元信息被储存在空间中。元空间没有使用堆内存。而是与堆不相连的本地内存区域。所以理论上系统可以使用的内存有多大,元空间就有多大。所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的。永久带的调优是很困难的。虽然可以设置永久代的大小。但是很难确定一个合适的大小。因为其中影响的因素很多。比如类的数据多少?常量数量的多少等。
因此,在JDK8直接将本地内存作为元空间的区域。物理内存有多大,元空间内存就可以有多大,这样永久的原空间分配问题就解决了。
浙公网安备 33010602011771号