java发展史上出现过很多垃圾收集器,各有各的适配场景,不仅适用于开发,也适用于运维。今天简单介绍一下java的内存布局和各种垃圾收集器的原理。JVM内存布局JVM在概念上大致分为6个(逻辑)区域:这6个区域根据是否被线程共享可以分为两类:一类是每个线程独占的:PC寄存器:也称为程序计数器,它记录了每个线程当前执行的指令字母。eg:当前执行的是哪条指令,接下来应该取哪条指令。JVMStack:又称虚拟机栈,在每个栈帧(Frame)中记录了局部变量、方法返回地址等。NativeMethodStack:本地(native)方法栈,顾名思义,就是调用操作系统的native方法时所需要的内存区域。以上三类区域的生命周期与Thread相同,即创建线程时,对应的区域分配内存,线程销毁时,释放对应的内存。另一类是所有线程共享的:Heap:众所周知的堆内存区,也是GC垃圾回收的主要场所,用于存放类实例对象和Arrays实例等。MethodArea:方法区,主要是存放类结构、类成员定义、static静态成员等。RuntimeConstantPool:运行时常量池,如:strings、int-128~127范围值等,属于MethodArea的一部分。Heap和MethodArea在虚拟机启动时创建,虚拟机退出时释放。总之,程序运行时,内存中的信息大致可以分为两类。一种是与程序执行逻辑相关的指令数据。这类数据通常很小,生命周期很短;另一个是与对象实例相关的数据。数据可能很大,可以长时间被多个线程重复共享,比如字符串常量、缓存对象。将这两类不同特性的数据分开管理,体现了软件设计中“模块隔离”的思想。例如,我们通常将后端服务与前端网站解耦,这样也更便于内存管理。GC垃圾回收原理1.哪些内存区域需要GC?线程独占区:PCRegister、JVMStack、NativeMethodStack,都和线程有相同的生命周期(即:liveanddiewiththreads),所以不需要GC。线程共享的Heap区和Method区是GC关注的重点对象。2.常用的GC算法(1)mark-sweepmark-sweepmethod如上图,黑色区域代表需要清理的垃圾对象,标记后直接清空。这种方法简单快速,但缺点也很明显,会产生大量的内存碎片。(2)mark-copy标记复制方法的思路也很简单。将内存一分为二,始终留一块空(上图中右侧),将左侧(浅灰色区域)存活的对象复制到右侧。然后左侧全部清除。避免了内存碎片的问题,但是内存浪费非常严重,相当于只使用了50%的内存。(3)mark-compactmark-organization(也称mark-compression)方法避免了上述两种算法的缺点。垃圾对象被清理后,剩余存活的对象同时被清理并移动(类似Windows磁盘碎片整理),保证它们占用的空间是连续的,从而避免内存碎片问题,但是整理过程也会降低GC的效率。(4)generation-collect分代收集算法以上三种算法各有优缺点,并不完美。在现代JVM中,它经常被综合使用。经过大量的实际分析,发现内存中的对象大致可以分为两类:一些生命周期很短,比如一些局部变量/临时对象,而另一些则会存活很长时间,一个典型的例子就是websocket长连接中的连接对象,如下图:垂直的y轴可以理解分配内存的字节数,水平的x轴可以理解为时间的流逝(伴随着GC),可以发现大部分对象其实都是很短命的,很少有对象在GC中存活下来。因此,诞生了生成的想法。以Hotspot为例(JDK7):内存分为三大块:新生代、老年代和永久代。YoungGeneration更细分为三个区域:eden、S0、S1。结合我们经常使用的一些jvm调优参数,一些参数可以影响的各个区域的内存大小如下图示意图:梳理一下GC的主要流程。
