大咖分享

垃圾回收,从JVM开始
北大青鸟总部

摘要:所谓垃圾回收,指的是JVM释放掉那些没有引用对象做占用的内存空间,给新的对象使用。

关于垃圾回收分类,相信大家还是比较有感触的。自2020年5月1日起,北京公布了《北京市生活垃圾管理条例》,单位和居民不执行垃圾分类将面临处罚。在垃圾回收条例中,从之前的可回收垃圾、不可回收垃圾,升级为把垃圾明确分为了厨余垃圾、可回收垃圾、有害垃圾、其它垃圾四大类。每个人都要养成垃圾分类、垃圾回收的意识。事实上在互联网程序的世界里也存在垃圾回收的概念,那便是我们中大型应用开发必用、使用的应用程序占比超过80%的开发语言JAVA了。


所有的Java程序都是运行在JVM(JAVAVirtualMachine,Java虚拟机)中的,在Java程序运行时,JVM会为每个对象创建一块内存,并维护管理这块内存。现在最新的JDK版本(JAVADevelopmentKit,Java开发工具包,JVM运行在JDK上)已到JDK13及以上了,因此通过下图(JDK1.8)可以来看看JVM的内存模型。在内存模型中包含堆、虚拟机栈本地方法栈、方法区、程序计数器等等元素,堆主要用于存放所有的对象实例,实例创建后在此分配内存;虚拟机栈是Java方法执行的内存模型,每个Java方法在执行时都会同时创建一个栈帧,来存储局部变量表、操作栈、动态链接、方法返回地址等;本地方法栈则是服务于虚拟机使用到的Native方法;方法区是存储虚拟机加载的元数据信息;元数据区主要是存储方法名、方法变量等元数据信息;程序计数器主要是指明当前线程所执行的字节码。因为Java对象主要在Heap堆区中运行,因此总的来说,JVM管控的内存就是Heap内存、Non-Heap内存。



一个Java程序的执行如下图所示,通过编译成class文件,调用JVM的类加载子系统,在内存空间中创建对象、执行对象,执行结束后,进行对象的内存回收,也就是垃圾回收(GarbageCollection)



所谓垃圾回收指的是JVM释放掉那些没有引用对象做占用的内存空间,给新的对象使用参照垃圾分类的思想,在JVM中也会根据对象的存活周期将对象分代,包含年轻代、老年代、持久代,通过内存分代,可以更好的执行垃圾回收,提高效率。年轻代用来存储最近新创建的对象,在年轻代中存在的对象死亡是非常快的,为了提高年轻代中的垃圾回收效率,年轻代又会划分出Eden、Survivorfrom、Survivorto三个区域,年轻代的垃圾回收成为MinorGC;老年代用来存储存活很久的对象,老年代的垃圾回收成为MajorGC;持久代存储JVM运行时的需要的Java库的类和方法,一般由Java厂商指定,在JDK1.8之后,改名为metaspace元空间。



在JVM中内存回收过程大概如下:对象在年轻代EdenSpace创建,当EdenSpace内存空间满的时候,垃圾回收器就把所有在EdenSpace中的对象都扫描一次,把有效的对象复制到第一个SurvivorSpace(即Survivorfrom),无效的对象所占用的空间进行释放。当EdenSpace再次变满的时候,把EdenSpace中有效的对象移动到第二个SurvivorSpace(即Survivorto),同时第一个SurvivorSpace的数据也会移动到第二个SurvivorSpace。如果填充到Survivorto的对象被Survivorfrom或EdenSpace中的对象引用,那么系统会认为它们生命周期比较长,就会把它们移动到老年代。经过年轻代和老年代来来回回的新建、回收(MinorGC、MajorGC),不断的释放内存给程序用,如果不能够释放足够的内存空间,就会执行FullGC,把所有在堆中运行的线程清除。


那么年轻代和老年代又是使用什么算法进行垃圾回收的呢?在年轻代中使用的是复制-清除算法,所谓复制清除算法指的是在执行MinorGC时,先将应用程序挂起,将Eden和Survivorfrom中存活的对象复制到Survivorto当中,然后清除掉Eden和Survivorfrom中的对象,MinorGC是很频繁的。在老年代使用的是标记-清除算法,在执行MajorGC时,把对象标记出来,然后清除掉,MajorGC是比较低频的。值得注意的是在GC时会产生很多内存碎片,如果有较大的对象要从年轻代进入老年代,但并不能找到合适的存储空间时,也会触发GC。


垃圾回收算法是在垃圾回收器中实现和运行的,在JDK厂商(包含OracleJDK、HPJDK、IBMJDK)中也提供了不同的垃圾收集器。对于年轻代实现分别是Serial收集器、ParallelScavenge收集器、ParNew收集器,在老年代中是SerialOld(Mark Sweep Compact)收集器、ParallelOld(PSMarkSweep)收集器、CMS(ConcurrentMarkSweep)收集器。


年轻代垃圾回收器中,Serial收集器是一种单线程收集器,使用单个GC线程进行垃圾回收,在垃圾回收时,将整个应用程序挂起(stopthe world),然后复制-清除;ParallelScavenge是多线程收集器,使用多个GC线程进行垃圾回收;ParNew也是一种多线程回收器,使用多个GC线程回收,可以和CMS一起使用。老年代垃圾回收器中,SerialOld是一种单线程回收器,使用单个线程进行标记-清除;ParallelOld是并行垃圾回收器,使用多个GC线程进行垃圾回收;CMS是并行标记垃圾回收器,它的特点是垃圾回收算法在后台不会暂停应用线程并实现大部分多垃圾回收。年轻代和老年代垃圾回收器之间的关系关联可以见下图所示。



垃圾回收真不是一件容易的事情啊!Java程序员可以通过开源免费的Jprofier、Jconsle等工具监控垃圾回收情况,也可以使用付费的APM功能来进行监控。在垃圾回收不正常时,可以运行参数设置JVM堆内存大小、新生代、持久代空间。使用-Xms可以设置堆的最小空间,-Xmx可以设置堆堆最大空间,-XX:NewSize可以设置新生代最小空间,-XX:MaxNewSize可以设置新生代最大空间,-XX:PermSize可以设置永久代最小空间,-XX:MaxPermSize可以设置永久代最大空间,-Xss可以设置每个线程的堆栈大小。比如java-Xmx 10240m -Xms 10240m-Xmn5120m就设置了JVM最大可用内存时10240M,初始化内存也是10240M,年轻代内存为5120M。



当你开始编程、爱上编程时,你会发现程序的世界和生活的世界是共通的。在JVM的垃圾回收中有分类、有回收规则、有监控工具、有管理规则,这和我们生活中的垃圾分类不正好是一样一样的吗?世界如此美妙,我等凡夫俗子就好好的生活,好好的编程吧~

相关阅读
一行JAVA代码如何运行起来?
垃圾回收,从JVM开始
一篇文章搞懂SpringMVC核心执行原理
为什么SpringBoot能集万千宠爱于一身?
大话设计模式之代理模式
热门推荐