目录
7、什么是STW?为什么gc要STW(Stop The World)?
1、JVM的内存模型
根据虚拟机规范,JVM的内存分为 堆、虚拟机栈、本地方法栈、程序计数器、方法区。
JDK 1.8 同 JDK 1.7 比,最大的差别就是:元空间取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
虚拟机的内存中主要有程序计数器、虚拟机栈、本地方法栈、堆和方法区。
什么是永久代和元空间?
方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,永久代和元空间就是出于不同jdk版本的实现。
说白了,方法区就像是一个接口,永久代与元空间分别是两个不同的实现类而已。只不过永久代是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类——元空间进行替代。
从上图中可以看到,永久代与堆中的老年代是连续的,这里的连续指的是物理地址连续,永久代本身并不在堆中。因此,老年代与永久代其中一个满了,都会触发Full GC。
由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出,会报出"java.lang.OutOfMemoryError: PermGen space "异常。
元空间(Metaspace),不再与堆连续,而是直接存在于本地内存中,也就是机器的内存。理论上机器内存有多大,元空间的野心就有多大。
Java8中,使用元空间替换永久代的原因:
在之前的版本中,字符串常量池存在于永久代中,在大量使用字符串的情况下,非常容易出现OOM的异常。此外,JVM加载的class的总数,方法的大小等都很难确定,因此对永久代大小的指定难以确定。太小的永久代容易导致永久代内存溢出,太大的永久代则容易导致虚拟机内存紧张。
2、如何判断对象是否可以回收(垃圾判断算法)
引用计数法:一个对象被其它变量所引用,计数+1。如果某一个变量不再引用它了,计数-1。 当计数为0时可以被回收。
存在问题:如果发生循环引用,如下图。A、B两个对象都不会被回收,计数都为1。
可达性分析算法 :首先会确定一系列的根对象(GC Root),按照从上至下的方式搜索被根对象集 合所连接的目标对象是否可达,如果该对象被跟对象直接或间接的引用,就 不能被回收,否则就会被回收。该算法可以有效地解决在引用计数算法中循 环引用的问题,防止内存泄漏的发生。
哪些对象可以作为GC Root:
虚拟机栈中引用的对象:各个线程被调用的方法中使用到的参数、局部变量等。
本地方法栈内 JNI(通常说的本地方法)引用的对象
方法区中类静态属性引用的对象 比如:Java类的引用类型静态变量
方法区中常量引用的对象 比如:字符串常量池(string Table)里的引用
所有被同步锁 synchronized 持有的对象
Java虚拟机内部的引用。
3、垃圾回收算法
1、标记-清除算法(Mark-Sweep)
优点:速度快
缺点:内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题,会造成内存溢出。
2、标记-整理算法(Mark-Compact)
标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
优点:没有内存碎片
缺点:速度慢、内存开销大
3、复制算法(Copying)
按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。
优点:速度快、不会有内存碎片
缺点:占用双倍空间
4、Java中的四种引用
强引用:强引用j就是我们常见的普通引用对象,我们程序中几乎所有的引用使用的都是强引 用。只要还有一个强引用指向一个对象,就表明该对象还活着,不能被回收
软引用:一个软引用中的对象,不会很快被JVM回收,当JVM内存不足时,才会对其进行回收
弱引用:比软引用拥有更短的生命周期。在垃圾回收时,只要发现弱引用,不管堆空间是否 足够,都会将对象进行回收。由于垃圾回收器是一个优先级很低的线程,因此不一定 会很快发现那些只具有弱引用的对象。
虚引用:虚引用 就是形同虚设它并不能决定 对象的生命周期。任何时候这个只有虚引用的对 象都有可能被回收。因此,虚引用主要用来跟踪对象的回收,清理被销毁对象的相关 资源。
5、造成内存溢出的常见原因
StackOverFlowError
解释:线程栈内存溢出,
原因:方法递归调用
OutOfMemoryError:Java heap memory
解释:堆内存溢出
原因:大量对象创建撑爆堆区
OutOfMemoryError:Metaspace
解释:方法区溢出
原因:大量类被加载
6、JVM堆内存垃圾回收
Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆 内存中对象的分配与回收。
堆中存放我们new出来的对象,存放的区域就是Eden区域,但这个区域存满以后就会做minor gc,首先通过可达性分析算法来判断是否是垃圾,如果能够找到引用就不是垃圾,找不到就是垃圾,minor gc过程中我们有一部分是垃圾一部分不是垃圾,会将这些垃圾对象清除,然后非垃圾对象会放到survivor区域的to中,并且将对象的分代年龄+1,分代年龄(用来区分对象生命周期的一种标识,比如parallel默认对象分代年龄达到15岁就放到老年代中)。此时一次minor gc结束。我们可以推断出,老年代做full gc的频率应该远远小于年轻代minor gc,老年代等到存放对象快满的时候会做full gc,扫描老年代中的对象是否是垃圾,如果都不是并且此时还有对象要进入老年代,那此时就会报OOM(内存溢出错误)。
新生代使用的复制算法,老年代使用的是标记整理算法。
7、什么是STW?为什么gc要STW(Stop The World)?
指的是GC事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应, 有点像卡死的感觉,这个停顿称为STW。可达性分析算法中枚举根节点(GC Roots)会导致所有Java执行线程停顿。
原因很简单,因为java进行垃圾回收时使用可达性分析,从GC Root向下判断对象是否有引用,如果不把所有线程进入safe points并阻塞起来就会出现对象上一秒没有引用被删除,后一秒又出现引用,导致错误的产生。
8、GC 相关参数