深入JVM之JVM垃圾收集

来源:互联网 发布:网络推广工资待遇 编辑:程序博客网 时间:2024/05/16 06:42

深入JVM之JVM垃圾收集

堆内存模型

Java中最大的特点在于其具备良好的垃圾收集特性,也就是GC是整个Java之中最重要的安全保障,它可以保障即使再蠢的开发者也能够写出合理的代码来。
整个JVM中的GC的处理机制:对不需要的对象进行标记,而后进行清除。

这里写图片描述
一定要记住在JDK1.8之后将最初的永久代内存空间取消了,以下为JDK1.8之前的空间组成:
这里写图片描述
取消永久代的目的:是为了将HotSpot与JRockit两个虚拟机标准联合为一个。

在整个JVM堆内存之中实际上将内存分为了三块:
●年轻代:新对象和没达到一定年龄的对象都在年轻代;
●老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大;
●元空间:像一些方法中的操作临时对象等,直接使用物理内存;
├ 最初的永久代是需要在JVM堆内存里面进行划分;

分区的目的:方便内存数据的管理。

GC回收流程(★★★★★)

所有的数据都会保存在JVM的堆内存之中,但是在实际的开发之中创建许多的临时对象,也会有一些常驻对象(单例)存在,所以为了保证GC的性能问题,对于GC的处理流程如下图所示。
这里写图片描述

对于整个的GC流程里面,那么最需要处理的就是年轻代与老年代的内存清理操作,而元空间(永久代)都不在GC范围内;
1.当现在有一个新的对象产生,那么对象一定需要内存空间,于是现在就需要为该对象进行内存空间的申请;
2.首先会判断伊甸园区是否有内存空间,如果此时有内存空间,则直接将新对象保存在伊甸园区;
3.但是如果此时伊甸园区的内存空间不足,那么会自动执行一个MinorGC操作,将伊甸园区的无用内存空间进行清理,当清理之后会继续判断伊甸园区的内存空间是否充足?如果内存空间充足,则将新的对象直接在伊甸园区进行空间分配;
4.如果执行了MinorGC之后发现伊甸园区的内存依然不足,那么这个时候会进行存活区判断,如果存活区有剩余空间,则将伊甸园区的部分活跃对象保存在存活区,那么随后继续判断伊甸园区的内存空间是否充足,如果充足,则在伊甸园区进行新对象的空间分配;
5.如果此时存活区也已经没有内存空间了,则继续判断老年区,如果此时老年区空间充足,则将存活区中的活跃对象保存到老年代,而后存活区就会出现有空余空间,随后伊甸园区将活跃对象保存在存活区之中,而后在伊甸园区里为新对象开辟内存空间;
6.如果这个时候老年代也满了,那么这个时候将产生MajorGC(Full GC),进行老年代的内存清理;
7.如果老年代执行了FullGC之后发现依然无法进行对象的保存,就会产生OOM异常“OutOfMemoryError”。

面试题:请解释“StackOverflowError”和“OutOfMemoryError”的区别?
把之前内容都说出来。

堆内存参数调整(调优关键)(★★★★★)

这里写图片描述
通过之前的分析可以发现,实际上每一块子内存区域中都会存在有一部分的可变伸缩区,其基本流程:如果空间不足了,则在可变的范围之内扩大内存空间,当一段时间之后发现内存空间没有这么紧张的时候,再将可变空间进行释放。
调优时要想办法将伸缩区最好别存在。

这里写图片描述

在整个堆内存的调整策略之中,有经验的人基本上都只会调整两个参数:“Xmx”(最大内存)、“Xms”(初始化内存)。
如果要想取得这些内存的整体信息直接利用Runtime类即可。

public class TestDemo{    public static void main(String[]args){        Runtime run=Runtime.getRuntime();        long maxMemory=run.maxMemory();        long totalMemory=run.totalMemory();        System.out.println("MAX_MEMORY="+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+"(兆)");        System.out.println("TOTAL_MEMORY="+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+"(兆)");    }}
结果为:MAX_MEMORY=7629963264(字节)、7276.6(兆)TOTAL_MEMORY=514850816(字节)、491.0(兆)

发现默认的情况下分配的内存是总内存的“1/4”,而初始化的内存为“1/64”;那么也就是说整个内存空间的可变范围(伸缩区):491.0M~7276.5M,那么现在就有可能造成程序的性能下降。最好的办法就是讲初始化内存与最大内存保持一致。

java -Xmx16G -Xms16G TestDemo

那么这个时候就避免了伸缩区的可调策略,从而提升了整个程序的性能。

范例:观察GC详细日志

java -Xmx16G -Xms16G -XX:+PrintGCDetails TestDemo

发现新生代占3/8,而老年代占5/8,而元空间直接物理内存。
这里写图片描述

下面再继续编写一个代码,观察GC的触发操作
这里写图片描述
这里写图片描述
这里写图片描述

范例;测试GC处理

import java.util.*;public class TestDemo{    public static void main(String[]args){        Random rand=new Random();        String str="hello world";        while(true){            str+=str+rand.nextInt(999999999)+rand.nextInt(999999999);            str.intern();//强制产生垃圾        }    }}

请保证你现在的内存空间小:java -Xmx10m -Xms10m -XX:+PrintGCDetails TestDemo
如果在日后的开发之中你发现你的程序执行速度变慢
●可视化的工具:\java\jdk1.8.0_74\bin\jvisualvm.exe;
这里写图片描述
这里写图片描述
这里写图片描述

●命令查看:jmap (jmap -heap PID);
进入cmd打开tasklist(windows)、ps(linux)查看得到Java程序的PID为5292
这里写图片描述

年轻代

所有的新对象都会在年轻代产生,如果年轻代的空间不足,无法产生对象,则会产生MinorGC和MajorGC(Full GC).
这里写图片描述

所有使用关键字new新实例化的对象一定会在伊甸园区进行保存,而对于存活区保存的一定是已经在伊甸园区中存在好久,并且经过了好几次了MinorGC还保存下来的活跃对象。那么这个对象将晋升到存活区之中,存活区一定会有两块空间,这两块空间的大小一定是相等的,目的:一块存活区为了晋升,另外一块存活区为了对象回收。这两块内存空间一定有一块是空的。(两个空间可以互换,而且不固定)

在年轻代中使用的是MinorGC,这种GC的算法采用的是复制算法。
这里写图片描述
通过以上的分析可以发现,在整个的处理过程之中,伊甸园区里面保存的对象有可能大部分都属于临时对象,那么就有可能频繁的发生MinorGC,所以在HotSpot虚拟机之中为了加快此空间的内存分配操作形式,所以采用了两种技术。
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

范例:改变存活区比率

java -Xmx10m -Xms10m -XX:SurvivorRatio=6 -XX:+PrintGCDetails TestDemo

这里写图片描述
但其实大部分情况下并不需要控制。

老年代

老年代主要是接收由年轻代发送来的对象,一般情况下经过了好几次的MinorGC之后还会保存下来的对象才会进入老年代。如果你要保存的对象超过了伊甸园区的大小,那么此对象也将直接保存在老年代之中。当老年代内存不足时,将引发FullGC(MajorGC)。老年代对象是常用对象,所以占的空间最大。
在老年代之中会采用两种算法结合的模式实现GC的处理:整理-压缩。
这里写图片描述

在回收清除的过程之中,发现所有在老年代之中被回收的对象并没有被进行空间的整理,所以老年代里面最头疼的问题就是碎片化问题。

这里写图片描述

以后在进行老年代存储的时候尽可能保存长期会被使用的对象,并且不会被轻易回收的大对象存在。

老年代内存调整参数
这里写图片描述

范例:设置老年代参数

java -Xmx10m -Xms10m  -XX:PretenureSizeThreshold=512k -XX:+PrintGCDetails TestDemo

此时如果超过了512K(1个空对象占8K的空间)的对象将直接被保存在老年代之中,也就是说这个对象不会再触发MinorGC。
这里写图片描述

范例:设置年轻代与老年代比率

java -Xmx10m -Xms10m  -XX:PretenureSizeThreshold=512k -XX:NewRatio=1 -XX:+PrintGCDetails TestDemo

这里写图片描述

永久代(JDK1.8后消失了)

虽然Java的版本是1.8,但是JavaEE的版本还是1.7呢。也就是说在JavaEE里面必须对永久代进行一些设置。永久代是在堆内存之中保存的,但是永久代不会被回收,例如:intern()方法产生的对象那么不会被回收。所以如果你的操作不当,导致永久代中的数据量过大,那么这个时候程序依然会跑出OOM(OutOfMemoryError)问题。
一般情况下,代码正常不会出现永久代问题。
这里写图片描述

但这些参数在JDK1.8之后是无效的。

范例:在JDK1.8之中设置永久代会出现错误提示

java -XX:MaxPermSize10m TestDemo

会出现如下错误提示:

Java HotSpot(TM) 64-Bit Server VM warning: ignoring optoion MaxPermSize10m;support was removed in 8.0

现在对于这些永久代的设置你只需要知道这些参数即可。

元空间

元空间是在JDK1.8之后才有的,其功能实际上和永久代没什么区别。唯一的区别在于永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存的限制。
这里写图片描述

范例:设置一些参数,让元空间出错

java -XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=1m TestDemo

这里写图片描述

此时会抛出“OutOfMemoryError:Metaspace”,空间不足。
注:以后的一些方法中的操作产生了大量的内存空间之后,这些空间不再受堆内存空间的影响,而都交给了直接的物理内存进行控制。

0 0
原创粉丝点击