美文网首页
深入理解JAVA虚拟机第五章

深入理解JAVA虚拟机第五章

作者: MaTae | 来源:发表于2020-01-19 11:27 被阅读0次

GC算法:

标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

复制-清除算法:朝生夕灭,将存活的对象复制进入另一块空闲的内存,然后将当前使用的内存进行清除。

标记-整理(压缩)算法:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法:分代灵活使用上面三种算法

分代:

年轻代:当一个对象创建的时候先进入的是Eden, 当第一次垃圾回收的时候进入 Servivor from ,当再次垃圾回收的时候利用复制算法到Servivor to区,然后 to 和from 更换位置实现复制算法,减少碎片浪费。对象每在Survivor区熬过一次GC,它们的年龄就加1,当对象年龄达到某个年龄(默认值为15)时,就会把它们移到老年代中。

老年代:使用标记整理算法,容量满的时候,会触发一次Major GC

永久代(jdk1.8以前):堆内存,不会被回收

元空间(jdk1.8):物理内存

常用收集器

一、新生代的收集器(复制算法)

Serial:单线程,必须暂停其他所有的工作线程,直到它收集完成

PraNew:Serial收集器的多线程版本

Parallel Scavenge:目标是到达一个可控制的吞吐量,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

二、老年代的收集器(标记整理算法)

Serial Old:单线程,Serial收集器的老年代版本,作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

Parallel Old:Parallel Scavenge收集器的老年代版本

CMS:并发收集,低停顿。

缺点是无法处理浮动垃圾。大量空间碎片产生,频繁FullGC。CPU个数少于4个时,CMS对于用户程序的影响就可能变得很大。

三、回收整个Java堆(兼顾新生代和老年代)

Garbage First(G1)收集器:

分代GC:分代收集器, 同时兼顾年轻代和老年代。

启发式算法:在老年代找出具有高收集收益的分区进行收集,根据用户设置的暂停时间目标自动调整年轻代和总堆大小

内存分区:将内存划分为一个个相等大小的内存分区,不要求年轻代或老年代的空间都连续,天然就是一种局部压缩方案

并行性: 回收期间, 可由多个线程同时工作, 有效利用多核cpu资源;

空间整理: 回收过程中, 会进行适当对象移动, 减少空间碎片;

可预见性: G1可选取部分区域进行回收, 可以缩小回收范围, 减少全局停顿。

串行收集器组合 Serial + Serial Old

并行收集器组合 Parallel Scavenge + Parallel Old

并发标记清除收集器组合 ParNew + CMS + Serial Old

案例:

一、过大的堆内存(12G+)进行回收时带来的长时间的停顿

判断:应该是使用了吞吐量优先收集器

解决方案:

程序Full GC频率控制得足够低,深夜执行定时任务的方式触发GC

改为CMS收集器进行垃圾回收

在一台物理机器上建立多个小内存虚拟机配置成逻辑集群(可能会导致全局资源竞争IO异常)

二、集群间同步导致的内存溢出

检查方式:服务带着-XX:+HeapDumpOnOutOfMemoryError参数启动

主要原因:使用了全局同步缓存,当网络情况不能满足传输要求时,重发数据在内存中不断堆积,很快就产生了内存溢出。

解决方案:支持频繁读,不要频繁写,尤其是全局同步

三、堆外内存导致的内存溢出错误

检查方式:jstat、系统日志

主要原因:内存(2G)太多分给了堆(1.6G),直接内存很少(<0.4G),直接内存只能在[堆的老年代满了后]执行Full GC时顺便清理,或者在抛异常的时候执行System. gc()

间接原因:大量的NIO操作需要使用到直接内存

解决方案:留一些内存给直接内存

四、外部命令导致系统缓慢

检查方式:mpstat工具发现CPU使用率很高,并且系统占用绝大多数的CPU资源的程序并不是应用系统本身,通过Solaris 10的Dtrace脚本可以查看当前情况下哪些系统调用花费了最多的CPU资源。

主要原因:执行shell脚本是通过Java的Runtime.getRuntime().exec()方法来调用的,Java虚拟机首先克隆一个和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。

解决方法:不要使用shell,用java api去调用

五、服务器JVM进程崩溃

检查方式:系统日志

主要原因:Web调用超时,等待的线程和Socket连接越来越多,最终在超过虚拟机的承受能力后使得进程崩溃

解决方法:

服务提供方修复接口

将异步调用改为生产者/消费者模式的消息队列实现

缩小超时时间

六、不恰当数据结构导致内存占用过大

检查方式:GC日志

主要原因:80MB的数据文件到内存进行分析,形成超过100万个HashMap,Minor GC就会造成超过500毫秒的停顿,ParNew收集器“朝生夕灭”,会把对象复制到Survivor

解决方法:

将Survivor空间去掉,Minor GC后立即进入老年代,Major GC的时候再清理它们

更换数据结构,不使用HashMap(空间效率为16B/88B=18%)

七、由Windows虚拟内存导致的长时间停顿

检查方式:加入参数-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps-Xloggc:gclog.log后,从GC日志文件解析

主要原因:Windows程序在最小化时它的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文件的操作而导致不正常的GC停顿。

解决方法:加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”

Eclipse运行速度调优

升级JDK,虚拟机版本上升带来优化

不需要在加载的时候再进行字节码验证,因此通过参数-Xverify:none禁止掉字节码验证过程

把-Xms和-XX:PermSize参数值设置为-Xmx和-XX:MaxPermSize参数值一样,这样就强制虚拟机在启动的时候就把老年代和永久代的容量固定下来,避免运行时自动扩展

要求虚拟机在新生代和老年代分别使用ParNew和CMS收集器进行垃圾回收

相关文章

网友评论

      本文标题:深入理解JAVA虚拟机第五章

      本文链接:https://www.haomeiwen.com/subject/jfrrzctx.html