弄浪的鱼

为什么老年代的 Full GC 要比新生代的 Minor GC 慢很多倍,一般在 10 倍以上?我们可以从了解老年代常用的垃圾回收器 CMS 的工作原理开始回答这个问题。

与新生代采用的复制算法不同,CMS 采用的垃圾回收算法是标记整理算法。且老年代的垃圾回收更加复杂,总共分成 4 个阶段,它们分别是:初始标记、并发标记、重新标记、并发清理。

我们从 CMS 的垃圾回收基本原理开始,了解 CMS 是如何工作的。

我们知道当堆中内存满了的时候,JVM就会使用可达性分析算法,检查对象是否有被 GC Root 引用。如果一个对象没有被任何 GC Root 引用,那就说明它是一个无效的对象,就会被垃圾回收器回收。此外,即使是被一个 GC Root 引用,但如果是弱引用,那这个对象任然有被回收的风险。

现在我们知道了什么情况下一个对象会被回收,那 JVM 是怎么回收一个对象的呢?

重新回过头来看一些 JVM,思考一个问题:分出一个永久代我可以理解,为什么又要把堆分成新生代和老年代呢?私以为划分成新生代和老年代是因为它们里面对象的性质不同,要用不同的垃圾回收算法来回收效率才高,是为垃圾回收器服务才分出新生代和老年代的。

本文说明新生代、老年代和永久代中对象的特性,以及何时它们何时触发 GC。

我们知道多个线程同时读写同一共享变量会导致并发问题。

一种解决方案是使用 Immutability 模式,如果共享变量在初始化之后就不会改变,只能读取,那么无论多少个线程同时读这个共享变量都不会出现并发问题。比如说 Java 中的 Long、Integer、Short、Byte 等基本数据类型的包装类的实现。

另一种解决方案是突破共享变量,没有共享变量就不会有并发问题。那么如何避免共享呢?思路其实很简单,就是每个线程拥有自己的变量,彼此不共享,就不会有共享问题。

具体来说有两种方法:线程封闭和线程本地存储(ThreadLocal)。

在正式了解 Java 类加载机制的细节之前,我们有必要了解一下 JVM 整体的运行原理,对 JVM 运行机制的整体脉络进行一次梳理。

JVM 整体运行原理

首先我们会有一系列以 .java 结尾的源文件。要把这些源文件发布到线上,我们需要将其打成 jar 包,或者打成 war 包。这个打包的过程就是将 .java 文件编译.class 文件。接着就可以使用 Tomcat 这样的容器,或者 java 命令来运行一个 jar 包中的文件。

这里有一个问题,编译好的 .class 字节码是怎么运行起来的呢?