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

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

内容简介:(不过,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垃圾回收策略》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Python 3面向对象编程

Python 3面向对象编程

[加]Dusty Phillips(达斯帝•菲利普斯) / 肖鹏、常贺、石琳 / 电子工业出版社 / 2015-6 / 79.00元

Python 是一种面向对象的解释型语言,面向对象是其非常重要的特性。《Python 3面向对象编程》通过Python 的数据结构、语法、设计模式,从简单到复杂,从初级到高级,一步步通过例子来展示了Python 中面向对象的概念和原则。 《Python 3面向对象编程》不是Python 的入门书籍,适合具有Python 基础经验的开发人员阅读。如果你拥有其他面向对象语言的经验,你会更容易理解......一起来看看 《Python 3面向对象编程》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具