Java -- Hotspot虚拟机调优与GC垃圾回收策略

栏目: Java · 发布时间: 6年前

内容简介:(不过,Java在高性能IO、大内存使用上还是有些自己的弱点(个人观点,有进一步见解的可留言讨论),不过大部分系统开发还是可以应付的,Hadoop、Hbase也都是java写的。所以必要过分的挣这些,回到正题哈,Java相对于c/c++来说,是比较“动态”的语言,在运行时期,也有扩展性和可优化性(不像c/c++直接编译成机器码)。所以,针对JVM和GC的一些优化策略就显得尤为重要,提供给程序员的灵活性也会相应的增加。这两天着手于Java后端进程的优化,对jvm和gc进行了一些研究。做了一些简单的总结:

( 先扯扯Java,热热身 ) 论坛上,经常看到有些人讨论c、c++、 java 哪个更快,哪个更主流等的口水贴,吵的乐此不疲。其实个人感觉Java 1.6之后性能和开发效率都提高了不少,虽然不像直接编译成机器码的语言一样,但是Java特有的JVM动态优化器、JIT即时编译器对热点代码都提供了动态编译和即时优化,而且开源的库也比较多,开发效率也比较高。

不过,Java在高性能IO、大内存使用上还是有些自己的弱点(个人观点,有进一步见解的可留言讨论),不过大部分系统开发还是可以应付的,Hadoop、Hbase也都是java写的。所以必要过分的挣这些, 应用的场景、底层相关度、团队开发的熟悉语言反而显得比较重要

回到正题哈,Java相对于c/c++来说,是比较“动态”的语言,在运行时期,也有扩展性和可优化性(不像c/c++直接编译成机器码)。所以,针对JVM和GC的一些优化策略就显得尤为重要,提供给 程序员 的灵活性也会相应的增加。这两天着手于Java后端进程的优化,对jvm和gc进行了一些研究。做了一些简单的总结:

  • 虚拟机的内存

  • GC回收算法、策略

  • jvm启动参数优化

  • 性能优化 Tips

1、虚拟机的内存 :

熟悉c的同学,一定知道c对内存进行分区的管理:栈+堆+静态存储+mmap等。同样Java亦是如此。不过Java绝大多数对象都是new出来得,所以Java与"堆内存"联系更紧密。也是吃内存的大户,对“堆内存”的分区方式有些不同,Java把堆分成了四大部分:

Eden(新生代) + S0/S1(Survivor区域) + Old(老年代) + Perm(持久代)

Java -- Hotspot虚拟机调优与GC垃圾回收策略

Eden: Eden主要存储一些“新对象”, 比如刚刚被new出来的(就像伊甸园的新生人类一样) 。大部分生命周期比较短的对象,都是在这个区域里徘徊。

S0/S1: 又称为from和to是两个同等大小的区域, 在使用“复制”回收算法时,作为DoubleBuffer(双缓冲见博客之前的文章) ,起内存整理的作用(具体作用后面gc算法时会提到)。

Old: 老年代主要存储一些生存时间特别长的对象,比如伴随服务进程时刻一直存在的对象,还有进入Eden后,长时间没被清理的对象,也会进去老年代。或者超大的对象无法直接在新生代分配的对象。

Perm: 存放代码,字符串常量池,静态变量等,可以持久化的数据。(包含String.intern()方法放入字符串常量池的容量)。Perm区不同于"方法区",方法区按Java规范属于Non-Heap,只是SunJDK把它实现在了Perm区,用Perm区来存储(后续SunJDK正在逐步移出)。

Java New IO ( NIO )为了获得更高的效率,防止jvm的堆内存和系统内存做多一层的映射, 使用了DirectMemory的方式 。例如NIO中的MappedByteBuffer,DirectByteBuffer。直接从操作系统分配内存,也成为“ 堆外内存 ”。这部分内存不受GC的直接管理,但是效率很高。使用时要比较小心,否则有可能堆内存还剩很多的时候,却抛出了OutOfMemory的异常(无法分配内存了)。

2、GC回收算法、策略 :   

Garbage Collection时时刻刻伴随这你写的代码,帮你回收着不会再使用的对象。在c/c++中,malloc/free和new/delete总是要成对的出现(自己的东西自己收拾)。在Java GC伴随中写代码的程序员, 基本上不用考虑自己“收拾”了,也基本上不用担心哪里忘了"free"内存 。注意,是“基本上”,因为有时候错误的使用,也会造成Java的内存泄漏。

     试想一下,如果你自己写一个垃圾回收器,你会怎么做?(拓展一下思维哈)

     首先,我们需要明确,什么是垃圾对象?什么是内存的泄漏?狭义的理解,可以简单的认为,如果一个对象,我们以后不可能在用了,不想要了(就像丢掉生活中的垃圾), 把这个对象的“引用”赋值一个null,哇!世界清静了,再也找不到那个对象了 。泄漏,顾名思义,那个被你丢弃的对象,那块内存被你扔掉了,但是却没人能接着复用,就像从内存中扣除去了一样。所以,可以基本看出gc的简单的流程:

      遍历内存中所有的对象 -->  找到那些你不在需要的(引用为null) --> 清理那块内存(不保证一定) --> 放入未使用的内存供其他地方用

     这就是GC的大致流程,当然其中的很多不同的算法细节造就了不同的结构、效果:

     

一、遍历对象,找到“垃圾”所使用的方法:

*引用计数法(经典,但是Sun Java未使用):

               引用计数很好理解,就是为每一个对象维护一个计数器,存储引用这个对象的个数,如:

             A a = new A();  // new出来的这个对象“X”的引用就为1

             A b = a ;  // “X”引用+1

             a = null;  // “X”引用-1

              当对象“X”的引用为0,说明没人再引用它,它就没用了。

             *  根搜索法(Sun Java使用):

   

             此算法中,所有的Java对象构成一颗近似“搜索树”的结构,有一个root根节点,每次从root出发向下搜索,当整个树遍历完成后,那些不在其中的变量则视为"垃圾"。

             如下对象可作为root可达的对象:

                        Java虚拟机栈中变量所引用的对象(比如A a = new A(),a即为栈中变量)  -- 最主要的

                    方法区中静态属性引用的对象

                    方法区中常量引用的对象

                    JNI Native方法引用的对象

二、回收算法:

标记-清除 算法 (Mark-Sweep算法)

分为两个阶段:标记和清除,标记就是利用上述方法先找到所有人为是垃圾的对象,然后进入清除阶段,清理每块内存。是所有算法中最基本的,其他算法都是在它基础上演进的。可以看出它所存在的问题:

1、效率不高,遍历过程需要Hung住整个JVM(暂停进程执行)

2、会产生碎片,因为清理过程较简单,只是回收不会把不连续内存合并,有可能利用不了两块内存中间的空隙容量(如下图,灰色之间的白色区域不够新分配)

Java -- Hotspot虚拟机调优与GC垃圾回收策略

复制算法 (Eden、S0/S1使用的算法)

该方法分配两块大小相同的内存A和B,同一时刻只用A或者B,另外一块作为Buffer,不写入数据。写满回收时,将仍然“活着”的对象从A移入B,移入的时候,可以将所有对象“整齐”的排放,相当于一次整理,然后一次性的清理整个A内存,B代替A的地位寸处对象,A作为Buffer等待下次交替。

可以避免碎片的问题,效率也不错,不过会浪费1/2的内存块,因为要作为buffer不能使用。所以这种方法不适合老年代这种大内存的地方,而且不适合长生命周期的对象,因为需要在两块内存之间拷贝多次。适合新生代这种比较小的内存块,不久之后将被回收,这就是就是S0/S1的实现方法。

Java -- Hotspot虚拟机调优与GC垃圾回收策略

标记-整理 算法 (Mark-Compact)

该方法第一步与标记-清除类似,第二步整理时,不直接清除内存,而是把所有存活的对象向一个固定地方聚齐(整理),就像收拾屋子一样,妈妈总是会把孩子们先喊到屋子一角,然后开始打扫。

整理过程不需要另外一块内存buffer的参与,而且不会由于长时间存活的对象而造成频繁移动拷贝。所以适合老年代。

Java -- Hotspot虚拟机调优与GC垃圾回收策略

 概念整理:

FullGC: 老年代的触发的GC,可回收老年代和Perm代

YoungGC: 年轻代的GC,又称为MinorGC

MinorGC可能比较频繁一般多一些没关系, FullGC需要Hung住进程,发生多了影响响应时间,所以应该尽量避免

可以通过设置-Xms(初始化内存大小)和-Xmx(最大内存大小)使堆定长,这样就会发生收缩和扩张,可以避免GC的发生。

GC总览:

Java -- Hotspot虚拟机调优与GC垃圾回收策略

jvm启动参数优化

几种算法各有各的优势,并且根据内存分区不同而选择不同的算法,下面给出一些JVM和GC启动时候的参数,可以帮助调优程序对内存的使用:

-XX:-DisableExplicitGC禁止调用System.gc(),可避免强制的无用GC

-XX:+ScavengeBeforeFullGC      新生代GC优先于Full GC执行

-XX:+UseConcMarkSweepGC    对老生代采用并发标记清除算法进行GC

-XX:+UseParallelGC                      启用并行GC

-XX:+UseParallelOldGC                对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用

-XX:+UseSerialGC启用串行GC

-XX:+PrintGC  每次GC时打印相关信息

 -XX:+PrintGC Details每次GC时打印详细信息

-Xloggc:gc.log    GC打印文件

-XX:+HeapDumpOnOutOfMemoryError内存溢出时dump文件,可供分析

--server 以server模式启动(默认client),会触发很多优化机制(JIT编译、优化器),适合启动时间长,运行响应快的后端进程。 建议后端都开启。

性能优化Tips

1、在适合的场景中选择合适的GC算法,优先使用并发GC,如CMS(-XX:+UseConcMarkSweepGC),性能好于并行GC,好于串行GC。

2、新对象在Eden和S0/S1经过15次YoungGC后,一次GC长一岁,进入Old。所以尽可能的释放无用的引用和资源。

3、Java String的subString和split方法由潜在的浪费内存的诟病,大量字符串操作情况下,自行用while和new String方式替换。

4、多使用并发数据结构(java.util.concurrenc),提高并发性能。

5、多线程下谨慎使用volatile 关键字,避免内存栅和内存的一致性访问

6、大数据量,高性能访问可以使用或借鉴Google Guava库

7、对直接数据类型,比如int、long大量操作时,避免与Integer、Long转换带来的装箱拆箱消耗

8、在高IO场景下,使用NIO代替原来的stream io

9、jvm喜欢可以重复调用的代码,可以做JIT即时编译和优化

10、构造HashMap如果元素个数可预先预估,比如cache,最好通过构造函数传入预估大小、调节负载因子防止rehash过于频繁。

11、rehash代价比较高,如果需要自己实现的话,可以参考一下 redis 的rehash方式,利用了double buffer,可实现动态rehashing过程。


以上所述就是小编给大家介绍的《Java -- Hotspot虚拟机调优与GC垃圾回收策略》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

美团机器学习实践

美团机器学习实践

美团算法团队 / 人民邮电出版社 / 2018-8-1 / 79.00元

人工智能技术正以一种超快的速度深刻地改变着我们的生活,引导了第四次工业革命。美团作为国内O2O领域领 先的服务平台,结合自身的业务场景和数据,积极进行了人工智能领域的应用探索。在美团的搜索、推荐、计算广告、风控、图像处理等领域,相关的人工智能技术得到广泛的应用。本书包括通用流程、数据挖掘、搜索和推荐、计算广告、深度学习以及算法工程6大部分内容,全面介绍了美团在多个重要方面对机器学习的应用。 ......一起来看看 《美团机器学习实践》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试