JVM内存结构和垃圾收集算法

JVM内存结构:
  堆、方法区和运行时常量池是所有线程共享访问的内存区域;而java栈、本地方法栈和程序计数器是运行时线程私有的内存区域

  

 

堆:

存放所有的Java 对象,存储对象实例。堆是一个运行时数据区,类的对象从中分配空间,这些对象通过New 关键字建立,堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存周期也不需要事先告诉编译器,Java 的垃圾收集器会自动收走那些不再使用的数据。缺点:在运行时动态分配内存,导致数据访问速度较慢。大多虚拟机里Java的对象和数组都存放在堆中。
在堆内分配存储相较于栈来说,有很大的灵活性。
由于堆是GC主要回收区域,GC极有可能会在大内存的使用和频繁进行垃圾回收过程上成为系统性能瓶颈。TaobaoJVM(GCIH( GC invisible heap )技术实现了off-heap,生命周期较长的Java 对象从heap中移到heap之外,并且GC 不能管理GCIH 内部的Java 对象)降低GC 的回收频率和提升GC 的回收效率。逃逸分析(Escape Analysis)与栈上分配同样也是很有效的方式。

方法区:

方法区内保存的信息大部分来自于C la ss 文件,主要保存的信息是类的元数据:

1.类型信息:类的完整名称、父类的完整名称、类型修饰符( pubIi c/protected/privat e )和类型的直接接口类表。
2.常量池:类方法、域等信息所引用的常量信息。
3.域信息:域名称、域类型和域修饰符。
4.方法信息:方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数技和方法栈帧的局部变量区大小以及异常表

当两个线程同时需要加载一个类型时,只有一个类会请求C lassLoader 加载,另一个等待
虽然被叫作永久区,但是在永久区中的对象同样也是可以被GC 回收的,只是对于GC 的对应策略与Java 堆空间略有不同,通常主要从两个方面分析: 一是GC 对永久区常量池的回收,二是永久区对类元数据的回收。

Java栈:

主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。
Java 虚拟机栈也是线程私有的内存空间,它和Java 线程在同一时间创建,它保存方法的局部变量、部分结果,井参与方法的调用和返回。管理Java 函数的调用。
是一种快速有效的分配存储方法,访问速度仅次于寄存器
Java 虚拟机必须知道被存储在栈内的所有数据的确切大小和生命周期,以便动态调整内存空间

程序计数器(Program Counter Register):

a.位于处理器内部,是线程独有的一块内存空间,生命周期与线程的生命周期保持一致。
b.由于处理器要执行的程序(指令序列)都是以二进制代码序列方式预存储在计算机的存储器中,处理器将这些代码逐条地抽取到处理器中再译码、执行,用以完成整个程序的执行。为了保证程序能够连续地执行下去, CPU 使用程序计数器来确定下一条取指指令的地址,又被称为“指令计数器”。
c.如果当前线程正在执行一个Java 方法,则程序计数器记录正在执行的Java 字节码地址,如果当前线程正在执行一个本地方法,则程序计数器为空。

本地方法栈:

a.异于JVM栈,它是管理本地方法的调用,本地方法并不是用Java 实现的,而是使用C 实现的

b.某个线程调用一个本地方法时,它就进入了一个全新的井且不再受虚拟机限制的世界,本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区,甚至可以直接使用本地处理器中的寄存器,或者直接从本地内存的堆中分配任意数量的内存。和虚拟机同样的权利


垃圾收集算法:


垃圾收集器将对象从内存中清除出去前,会运行的方法(Object类里面):protected void finalize() throws Throwable {}。
堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden -> From Survivor(s0) = To Survivor(s1)。
对象先在Eden(如果空间不够,执行一次Minor GC),大对象直接入Old,避免在Young发生大量内存拷贝(复制算法收集内存);年龄计数器,对象经过一次Minor GC就会进入Survivor,之后Minor GC一次年龄+1,年龄达到阀值对象进Old;动态判断对象的年龄(Survivor中年龄相同对象大小总和>Survivor一半,年龄>=此年龄的对象可直接进入Old)。
空间分配担保(进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC)
垃圾回收过程中,应用软件将处于一种Stop the World 的状态,应用程序所有的线程都会挂起,暂停一切正常的工作,等待垃圾回收的完成。

垃圾标记算法,GC 执行垃圾回收前区分出内存中存活和死亡的对象(1-2):

1.引用计数法:

对于一个对象,只要有任何一个对象引用了它,它的引用计数器就加1,当引用失效时,引用计数器就减1。对象A 的引用计数器的值为0,则回收
特征:垃圾对象便于辨识,能方便及时地回收垃圾,没有延迟性,但是需要单独的字段存储计数器,这样的做法增加了存储空间的开销。每次赋值都需要更新计数器,这增加了时间开销,还有最致命的就是不能检测到环的存在可能导致内存泄露(死亡对象间存在相互引用,永生了)

2.根搜索算法:

Hotspot 中,GCRoots对象集合中包含了5 个元素:Java栈内的对象引用、本地方法栈内的对象引用、运行时常量池中的对象引用、方法区中类静态属性的对象引用以及与一个类对应的唯一数据类型的Class 对象。
不可达的对象并非是“非死不可”,对象真正死亡至少要经历两次标记过程(判断对象是否有必要执行finalize()方法。任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行)

垃圾回收机制策略,GC执行垃圾回收(3-6):

3.标记-清除算法(Mark-Sweep):

由于被回收的无用对象所占用的内存空间有可能是一些不连续的内存块,就会产生一些内存碎片。而且在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续内存空间的工作效率低于连续的空间。

4.复制算法(Copying):

将正在使用的内存a中的存活对象复制到未被使用的内存块b中,再清除正在使用的内存块a中的所有对象,交换两个内存的角色。不会产生内存碎片,但是会对系统内存折半(解决了标记清除算法的痛点,又制造了新的痛点,这时候标记-压缩就来了)。
复制算法建立在存活对象少、垃圾对象多的前提下,所以被广泛应用于年轻代(),年轻代串行垃圾回收器的Survivor【大对象,或者老年对象会直接进入老年代,如果To 空间己满,则对象也会直接进入老年代。Eden空间中的剩余对象也是垃圾对象】。

5.标记-压缩算法(Mark-Compact):

先从根节点开始对所有可达对象做一次标记,再将所有的存活对象压缩到内存的一端,之后,清理边界外所有的空间。和标记-清除一样适用老年代,但总体执行效率比标记-清除高

6.分代收集算法(Generational Collecting):

根据垃圾回收对象的特性,把对象分为年轻代、老年代、持久代。即将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法以提高垃圾回收的效率(基于对对象生命周期的分析)

实时垃圾收集算法(避免垃圾收集时应用程序被挂起太久,影响用户体验或者系统的稳定性):

7.增量算法(incremental Collecting):

一次性处理所有的垃圾会造成系统长时间的停顿,增量算法把垃圾收集线程和应用程序线程交替执行,进程以分阶段的方式完成标记、清理或复制工作。但是线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

参考:

《深入理解JVM & G1 GC》

 

猜你喜欢

转载自www.cnblogs.com/shuG214xin/p/10883015.html