前言算法
本篇博客将结合博主在实际工做中对JVM的认识,以及前一段时间春节大流量下对Java后台服务的一些参数设置的一个总结。若是你对JVM还不熟悉,能够参考博主之前的博客:《对Java内存结构的一点思考和实践》缓存
JVM体系结构
网络
上图,包含了组成JVM的各个要素,下面咱们简单先来介绍下它们。多线程
类加载子系统:负责从文件系统或者网络上加载CLASS信息,加载的信息存放在内存空间中的方法区。并发
方法区:就是存放类信息、常量信息、常量池信息等。ide
Java堆:在JVM启动的时候会创建Java堆,Java堆是Java程序最主要的内存区域,也是JVM进行垃圾回收的核心区域,几乎全部的对象都放置在Java堆中,而且堆空间是全部线程共享的。工具
Java栈:每一个虚拟机线程都会有一个私有的栈空间,即Java栈。在Java栈中保存着局部变量、方法参数、方法调用、返回值等信息。总之,这个空间和多线程有关系,和方法的执行有关系。性能
本地方法栈:要知道一些Java类的实现是依赖于native方法的,也就是一般用C编写的本地方法,本地方法栈和Java栈相似。spa
垃圾收集系统:Java进行垃圾清理的机制,后文在详细介绍。线程
PC寄存器:寄存器也是每一个线程私有的空间,JVM会为每一个线程建立PC寄存器,在任意时刻,一个JAVA线程老是在执行一个方法,即当前线程的当前方法。实际上,若是当前方法不是本地方法,那么会将当前方法的信息存入PC寄存器。在PC寄存器中,实际上,就是一个指针的概念,表明了当前线程的一个执行环境。在多线程进行上下文切换的时候,PC寄存器就发挥做用了。
执行引擎:负责执行虚拟机的字节码。
堆、栈、方法区
能够说,在内存当中,咱们最为关心的就是堆、栈、方法区。下面咱们重点剖析下,它们三者之间的关系。
好比,有一个User类,有2个实例对象u1/u2,那么存储结构信息就如上图所示。
堆,解决的是数据存储的问题,即数据怎么放,放在哪里。即User类的2个实例对象都存放在堆中。
栈,解决的是程序的运行问题,即程序如何执行。说白了,u1/u2这2个局部变量,对真实对象的引用就存放在栈中。
方法区,是堆、栈的一个辅助区域,或者说是先决条件,没有类信息等,如何建立对象呢。
Java堆能够细分为新生代、老年代。其中新生代存放新生的对象或者年龄不大的对象;老年代则存放老年对象。新生代分为Eden、S0、S1这三个区域,SO/S1也称为from/to区域。S0/S1这两个区域是大小相等而且能够互换角色的空间,在后文的复制回收算法中在详细描述它们。
在绝大多数状况下,对象首先分配在Eden区域,在一次新生代回收后,若是对象还存活,则会进入S0/S1区域,以后每通过一次新生代回收,若是对象存活,它的年龄就加一,当年龄达到阀值后,就会进入老年代。
Java栈的结构
Java方法区,也称之为永久区(Perm)。方法区,它保存系统的类信息,好比类的字段、方法、常量池等信息。方法区的大小决定了系统能够保存多少个类,若是系统定义了太多的类,而方法区空间不足,就有可能抛出内存溢出错误。
JVM参数设置
-XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC,就会打印日志
-XX:+PrintGCDetails 能够查看详细信息,包括各个区的状况
-XX:+PrintCommandLineFlags 将给虚拟机设置的参数所有打印出来
-Xms200m 设置JAVA程序启动时初始堆大小
-Xmx500m 设置JAVA程序能够得到的最大堆大小
注意:在实际开发中,咱们通常状况下,都会将堆的初始值和最大值设置成同样的。这样的好处在于减小程序运行时的垃圾回收次数,从而提升性能。
-Xmn70m 设置新生代的大小
注意:若是设置一个比较大的新生代,那么势必减小老年代的大小,因此这个参数对系统性能以及GC行为有很大影响。在实际中,通常将新生代大小设置为整个堆大小的1/3到1/4左右。也就是说新生代:老年代=1/2 到 1/3。总而言之,咱们应该尽量将对象预留在新生代,减小老年代的GC次数。
-XX:SurvivorRatio 用于新生代中Eden/from=Eden/to的比例
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/log/xxx.dump
开启堆内存溢出OOM错误导出堆信息功能,并打印到指定路径下
注意:实际上这个堆内存导出文件,能够用相关内存分析工具进行分析。
-Xss1m 线程的最大栈空间
注意:这个参数直接决定了方法能够调用的最大深度,特别是递归调用。
-XX:PermSize=64M
-XX:MaxPermSize=64M 方法区的初始、最大大小设置
-XX:MaxDirectMemorySize
注意:这个参数用于配置直接内存。什么是直接内存,想想NIO,你就会明白。若是不设置,默认就是最大堆大小。所以若是直接内存若是没有有效释放空间,或者达到堆空间的最大大小,就会OOM。
-client/-server 事实上,在JDK1.7 64之后,就已经没有这方面的事情了。
-XX:MaxTenuringThreshold=15 设置新生代对象进入老年代的对象的年龄阀值,默认就是15次
-XX:PretenureSizeThreshold
若是对象的大小比较大,没法在Eden直接分配呢?会直接进入老年代!可是须要注意TLAB的优先分配。
-XX:+UseTLAB
-XX:+PrintTLAB
-XX:+TLABSize
使用、打印、设置TLAB大小。后文会介绍TLAB。
-XX:UseSerialGC 设置新生代、老年代使用串行回收器
-XX:UseParNewGC
-XX:ParallelGCThreads
设置新生代ParNew回收器,以及回收线程的个数
-XX:UseParallelOldGC
CMS相关参数后文介绍。
GC
垃圾收集算法
引用计数法
一句话,若是对象存在被引用,则计数器加一,失效则减一;若是计数器为0,就能够回收。
没法解决循环引用的问题,并且计数器常常加减,操做频繁,性能不佳。实际工做中并无使用。
标记清除法
分为标记、清除阶段。那么怎么标记呢?并非采用上面的引用计数法,而是基于ROOT树寻找路径来肯定是否应该被标记。被标记将被清除,这会致使空间碎片的问题。垃圾回收后的空间不是连续的,显然不连续的内存空间的效率要低于连续内存空间的效率。
复制算法
核心思想就是将内存空间分为2块,每次只是用其中一块。在垃圾回收时,将其中一块A的没法回收的留存对象所有COPY到另外一块B内存中,而后清除块A内存中的全部对象。垃圾回收时,反复去交换这2块空间的角色,完成垃圾收集工做。这里,显然避免了内存碎片的问题,可是须要注意的是复制的成本。若是有不少对象须要复制呢?咱们知道新生代的不少对象很不稳定,是会被频繁回收的,所以对于新生代而言,这种算法的复制成本比较小,因此被普遍应用。相比老年代而言,大量的对象是稳定的,甚至是不会被回收的,所以复制成本太大了,不适合。
标记压缩法
在标记清除算法的基础上作了些改进,就是把存活的对象压缩到内存的另外一端,然后进行垃圾清理,从而避免了内存碎片的问题。事实上,老年代采用的就是这种算法。
其实,为何分为新生代、老年代?说白了,就是想根据对象的特色进行分类,不一样的特色就可使用不一样的算法,这就是分代的好处。对于新生代、老年代而言,新生代回收频率很高,可是每次回收耗时很短;而老年代回收频率很低,耗时会相对较长,因此应该尽可能减小老年代的GC。
停顿现象
垃圾回收的任务就是识别和回收垃圾对象,完成内存清理动做。为了让GC高效的执行,大部分状况下,会要求系统进入一个停顿的状态,也就是暂停了全部的应用线程。由于只有这样才不会有新的垃圾产生,同时停顿也保证了系统在某一瞬间的一致性,有益于垃圾的标记。所以在进行GC的时候,会产生应用程序的停顿。
TLAB
Thread Local Allocate Buffer,线程本地分配缓存,一个线程专用的内存分配区域。在实际中,每一个线程势必会用到内存,为何不提早就为每一个线程建立一个较小的专属的内存区域呢?没必要等到线程使用到内存的时候在去申请。这样的话,会加速线程的运行速度,并且也避免了多线程的问题。固然,TLAB区域并不会太大。
垃圾收集器
串行回收器
所谓串行,就是单线程进行垃圾回收工做。新生代、老年代均可以使用。若是机器的并行性能比较差,能够考虑这种,由于它的专一性、独占性会有不错的表现。
并行回收器:ParNew
在串行回收器的基础上进行功能加强,就是使用多线程。适用于新生代。显然,对于并行能力较强的计算机而言,会有效缩短垃圾回收的实际时间。
并行回收器:ParallelGC
多线程独占+复制算法,新生代回收,很是关注系统的吞吐量,由于它提供参数来进行吞吐量的控制,好比设置最大垃圾收集停顿时间-XX:MaxGCPauseMillis。
并行回收器:ParallelOldGC
多线程独占+标记压缩算法,老年代回收。
目前最主流的回收器:CMS
CMS,即Concurrent Mark Sweep,并发标记清除,采用标记算法,适用老年代,关注系统停顿时间。
须要注意的是,CMS并非独占的回收器,也就是说CMS回收过程当中,应用程序仍然在不断工做,这是CMS回收器的一大优势。也正由于如此,CMS回收,应用程序不停顿运行,垃圾继续会产生,须要足够的内存空间。CMS并不会等到应用程序饱和才开始回收,而是根据指定的阀值开始回收。好比-XX:CMSInitiatingOccupancyFration设置为68,就是说当老年代空间使用达到68%的时候,CMS开始回收。另外,CMS为了不标记算法产生的内存碎片问题,提供了功能,好比-XX:+UseCMSCompactAtFullCollection可让CMS回收一次后进行内存碎片整理,-XX:CMSFullGCBeforeCompaction参数能够设置多少次CMS回收后,对内存进行一次压缩整理。
G1回收器
目前应用并不普遍。其主要的思想在于分区算法,把内存区域划分为N多个小的独立的空间,其实是想细粒度的对内存进行划分,这样就能够细粒度的回收,而不是对整个空间进行垃圾回收,从而提高性能,并减小了GC停顿时间。
到这里,JVM总结就结束了,但愿对你有用吧~