Java基础:JVM

jvm结构:

 

Java基础:JVM_第1张图片

  •  类加载子系统与方法区:类加载子系统(Classloader)负责从文件系统或者网络加载class信息,加载的类的信息存放于方法区,还会存放运行时的常量池信息,包括一些字符串字面值和数字常量(这部分信息是class文件中常量池部分的内存映射)
  • java堆:在虚拟机启动时建立,它是java主要的内存工作区域,几乎所有的java对象实例都存放在这里,堆空间是所有线程共享的
  • 直接内存:java的NIO库允许java程序使用直接内存,直接内存是在java堆外,直接向系统申请的内存空间,通常访问直接内存的速度会由于java堆,因此,出于性能考虑,对于读写频繁的场合会考虑使用直接内存。因为直接内存位于java堆外,因此,他的大小不会直接受限于Xmx所指定的最大堆大小,但是,系统内存是有限的,因此,直接内存和java堆的总和大小依然受限于操作系统所能给出的最大内存。
  • 垃圾回收系统:他是JVM的重要组成部分,垃圾回收器会对方法区,java堆,直接内存进行回收。其中java堆是垃圾回收器的工作重点,对所有对象空间的释放都是隐式的,对于不在使用的垃圾对象,垃圾回收系统会在后台默默地查找,标注并释放垃圾对象,完成对java堆,方法区,直接内存的全自动化管理
  • java栈:每一个java虚拟机线程都有一个私有的java栈,一个线程的java栈在线程启动的时候被创建,java栈中保存着帧信息,java保存着局部变量,方法参数,同时和java方法的调用和放回密切相关
  • 本地方法栈:和java栈十分类似,java栈用于java方法的调用而本地方法栈用于本地方法的调用(对当前操作系统API的调用,不同的操作系统有不同的本地方法,这也是为何JDK有多种版本,我们为何要根据当前操作系统安装对应的JDK版本),java虚拟机允许java直接调用本地方法
  • PC寄存器:她也是每一个线程私有的,java虚拟机会为每一个线程创建PC寄存器,每一个线程总是有一个方法正在被执行,如果当前方法不是本地方法,PC寄存器就会指向当前正在被执行的指令,如果是本地方法,PC寄存器的值为undefined,可以通过PC寄存器来去区分当前方法是不是本地方法
  • 执行引擎:它是java虚拟机最核心的组件之一(HotSpot),他负责执行虚拟机的字节码,现代虚拟机为了提升效率,会使用即时编译技术(JST) 先将方法编译为机器码后在执行

Java基础:JVM_第2张图片

java堆内存及分代

jvm的分代策略

java堆根据对象存活周期的长短分为几块:一般为新生代,老年代及永久代(对HotSpot而言)。

Java基础:JVM_第3张图片

为什么要分代?

堆内存是虚拟机管理内存最大的区域,也是垃圾回收最频繁的一块区域,java所有的对象都放在堆当中,对堆内存进行分代是为了提高对对象的内存分配和垃圾回收的效率。如果没有分代,生命周期长的和短的对象都放在一起,随着程序的执行,堆内存进行者频繁的垃圾回收,而每次都要对对象进行遍历,所耗费的时间是巨大的,会对生命周期短的对象进行垃圾回收,堆内存就会产生”碎片“,若新创建的对象较大,而”碎片“较小,就不能将该对象放在”碎片“中,会造成内存的浪费

有了内存分代就不同了,新创建的对象会放在新生代中,进过多次垃圾回收任然活下来的会放在老年代中,静态属性,类信息存放在永久代中,新生代的对象生命周期短,只需要在新生代中频繁的进行GC即可,对于老年代中的生命周期长的对象则不需要频繁的GC,对于永久代则不进行GC,还可以根据不同代的特点采用不要、同的垃圾回收策略,大大提高性能

永久代是HotSpot独有概念,(JDK1.5之前没有HotSpot,JDK1.7开始区去掉永久代,将常量池放在方法区中),它主要存放类信息,静态变量,数字常量,新生代和老年代是垃圾回收的主要区域

1.新生代(Young Generation)

新创建的对象放在新生区,新生区的对象大多生命周期很多,一次常规的GC可以回收70%-95%对象,回收率很高

HotPost将新生区分为三个部分,Eden,两个Survivor(from和to),空间比例默认为8:1:1,这样划分的目的是因为HotSpot采用复制清除算法来进行GC,可以充分利用空间,减少浪费

新创建的对象首先会被置于在Eden区(大对象直接放在老年区),当Eden区没有足够的空间进行分配时,虚拟机会发起一次Minor GC,当某一次GC开始时,只有Eden区和from区有对象,to区空白没有对象,当GC进行时,Eden区死掉的释放空间,存活下来的对象年龄加1并转移到to区同时清空Eden区,from区死掉的对象释放空间,存活下来的对象年龄加1,如果对象年龄=年龄阀值时,会被转移到老年区,如果年龄<年龄阀值(年龄阀值默认15,对象每经过一次GC,保存下来的年龄加1,年龄保存到对象的header),则会被复制(复制算法)到to区并清空from区,如果to区内存不足,则将对象转移到old区(作为担保),当GC结束时,Eden区和from没有对象,to区有对象;当下一次GC开始时,to区和from交换角色,做同样的事,

2.老年代

老年代中的对象来自新生代中年龄达到年龄阀值的对象或者被担保的对象,老年代中GC频率较低,回收率较低

3.永久代

存储类的信息,静态变量,常量等数据,一般不会进行垃圾回收

垃圾回收的算法

复制算法(Copying):

把内存空间分为两个相等的区域,每次只使用其中一个区域,GC时,存活下来的对象,复制到另一个区域,并整理对象的存储位置,不会出现”碎片“,同时清理当前使用区域。此算法的缺点是需要两倍存储空间。

标记清除法(Mark-Sweep):

此算法分为两个阶段,第一阶段从引用的根节点开始标记所有被引用的对象,第二阶段遍历整个堆,会将没有标记的对象清除,此算法的缺点是会暂停整个应用,并且会产生内存”碎片“

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

此算法结合了复制算法和标记清除算法的优点,也分两个阶段,只是没有内存”碎片“产生

垃圾收集器:

java中的垃圾收集器是对垃圾回收算法的具体实现,

  • Scavenge GC(次收集)和Full GC(全收集)的区别:

Scavenge GC:是发生在新生代的GC,使用速度快,效率高的算法,当Eden区内存紧张时会被触发;

Full GC:是发生在老年代的GC,当老年代内存级紧张时会触发全收集,也可以通过system.gc()来调用

JVM七个分代收集器:

Java基础:JVM_第4张图片

 三个新生代收集器

  •  串行收集器(Serial)

Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是:只用一个CPU(计算核心)/一条收集线程去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW)。可以使用-XX:+UseSerialGC打开。 虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停顿时间完全可以控制在几十毫秒~一百多毫秒内。

Java基础:JVM_第5张图片

       安全点:STW的点

  • 并行收集器(ParNew)

ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器)。

  由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads=参数控制GC线程数)

Java基础:JVM_第6张图片

  • Parallel Scavenge收集器

与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:
  系统吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
  停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge提供了如下参数设置系统吞吐量

三个老年代收集器

  • Serial Old收集器

Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法

  •  Parallel Old收集器 

Parallel Old是Parallel Scavenge收集器的老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在注重吞吐量及CPU资源敏感系统内使用

  • CMS收集器(Concurrent Mark Sweep)

 CMS是一种以获取最短回收停顿时间为目标的收集器(CMS又称多并发低暂停的收集器), 基于”标记-清除”算法实现, 整个GC过程分为以下4个步骤:
1. 初始标记(CMS initial mark)
2. 并发标记(CMS concurrent mark: GC Roots Tracing过程)
3. 重新标记(CMS remark)
4. 并发清除(CMS concurrent sweep: 已死对象将会就地释放, 注意:此处没有压缩)

CMS特点:
  1. CMS默认启动的回收线程数=(CPU数目+3)/4,当CPU数>4时, GC线程一般占用不超过25%的CPU资源, 但是当CPU数<=4时, GC线程可能就会过多的占用用户CPU资源, 从而导致应用程序变慢, 总吞吐量降低.
  2.无法处理浮动垃圾, 可能出现Promotion Failure、Concurrent Mode Failure而导致另一次Full GC的产生: 浮动垃圾是指在CMS并发清理阶段用户线程运行而产生的新垃圾. 由于在GC阶段用户线程还需运行, 因此还需要预留足够的内存空间给用户线程使用, 导致CMS不能像其他收集器那样等到老年代几乎填满了再进行收集. 因此CMS提供了-XX:CMSInitiatingOccupancyFraction参数来设置GC的触发百分比(以及-XX:+UseCMSInitiatingOccupancyOnly来启用该触发百分比), 当老年代的使用空间超过该比例后CMS就会被触发(JDK 1.6之后默认92%). 但当CMS运行期间预留的内存无法满足程序需要, 就会出现上述Promotion Failure等失败, 这时VM将启动后备预案: 临时启用Serial Old收集器来重新执行Full GC(CMS通常配合大内存使用, 一旦大内存转入串行的Serial GC, 那停顿的时间就是大家都不愿看到的了).
  3.最后, 由于CMS采用”标记-清除”算法实现, 可能会产生大量内存碎片. 内存碎片过多可能会导致无法分配大对象而提前触发Full GC. 因此CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).

  • 全区收集- G1收集器

 G1(Garbage-First)是一款面向服务端应用的收集器, 主要目标用于配备多颗CPU的服务器治理大内存.
  - G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS).
  -XX:+UseG1GC启用G1收集器.
  与其他基于分代的收集器不同, G1将整个Java堆划分为多个大小相等的独立区域(Region), 虽然还保留有新生代和老年代的概念, 但新生代和老年代不再是物理隔离的了, 它们都是一部分Region(不需要连续)的集合.如:

Java基础:JVM_第7张图片

 每块区域既有可能属于O区、也有可能是Y区, 因此不需要一次就对整个老年代/新生代回收. 而是当线程并发寻找可回收的对象时, 有些区块包含可回收的对象要比其他区块多很多. 虽然在清理这些区块时G1仍然需要暂停应用线程, 但可以用相对较少的时间优先回收垃圾较多的Region. 这种方式保证了G1可以在有限的时间内获取尽可能高的收集效率.
  G1的新生代收集跟ParNew类似: 存活的对象被转移到一个/多个Survivor Regions. 如果存活时间达到阀值, 这部分对象就会被提升到老年代.

其特定是:
  一整块堆内存被分为多个Regions.存活对象被拷贝到新的Survivor区或老年代.年轻代内存由一组不连续的heap区组成, 这种方法使得可以动态调整各代区域尺寸.Young GC会有STW事件, 进行时所有应用程序线程都会被暂停.
多线程并发GC.
G1老年代GC特点如下:
并发标记阶段
  1.在与应用程序并发执行的过程中会计算活跃度信息.
  2.这些活跃度信息标识出那些regions最适合在STW期间回收(which regions will be best to reclaim during an evacuation pause).
  3.不像CMS有清理阶段.
再次标记阶段
  1.使用Snapshot-at-the-Beginning(SATB)算法比CMS快得多.
  2.空region直接被回收.
拷贝/清理阶段(Copying/Cleanup Phase)
  1.年轻代与老年代同时回收.
  2.老年代内存回收会基于他的活跃度信息.

JDK的常用命令:

     jre中没有哦

1.jps

显示线程id和执行线程主类名

Java基础:JVM_第8张图片

2.jps -l

显示线程id和执行线程的主类名的全路径

Java基础:JVM_第9张图片 

3.jps-m

显示线程id和执行线程的主类名及其参数

Java基础:JVM_第10张图片

4.jstat -gc pid 

查看垃圾回收状况

 1 S0C: Survivor0(幸存区0)大小(KB)
 2 S1C: Survivor1(幸存区1)1大小(KB)
 3 S0U: Survivor0(幸存区0)已使用大小(KB)
 4 S1U: Survivor1(幸存区1)已使用大小(KB)
 5 EC  : Eden(伊甸区)大小(KB)
 6 EU  : Eden(伊甸区)已使用大小(KB)
 7 OC :老年代大小(KB)
 8 OU : 老年代已使用大小(KB)
 9 MC : 方法区大小(KB)
10 MU : 方法区使用大小(KB)
11 CCSC : 压缩类空间大小(KB)
12 CCSU : 压缩类空间使用大小(KB)
13 YGC:新生代GC个数
14 YGCT:新生代GC的耗时(秒)
15 FGC  :Full GC次数
16 FGCT:Full GC耗时(秒)
17 GCT :GC总耗时(秒) 

5.jvisualvm

JDK中最强大运行监视和故障处理工具,可以监控内存泄露、跟踪垃圾回收、执行时内存分析、CPU分析、线程分析

Java基础:JVM_第11张图片

  • jvm常见配置汇总
    1. 堆设置
      • -Xms:初始堆大小
      • -Xmx:最大堆大小
      • -XX:NewSize=n:设置年轻代大小
      • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
      • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
      • -XX:MaxPermSize=n:设置持久代大小
  • 收集器设置
    • -XX:+UseSerialGC:设置串行收集器
    • -XX:+UseParallelGC:设置并行收集器
    • -XX:+UseParalledlOldGC:设置并行年老代收集器
    • -XX:+UseConcMarkSweepGC:设置并发收集器
  • 垃圾回收统计信息
    • -XX:+PrintGC
    • -XX:+PrintGCDetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:filename
  • 并行收集器设置
    • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
    • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
    • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  • 并发收集器设置
    • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
    • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数

 Eclipse性能调优:

1.从github下载安装jvisualvm Visualgc插件    jvisualvm Visualgc:用来查看gc的插件

2.重启

3.通过配置启动

Java基础:JVM_第12张图片

Java基础:JVM_第13张图片

 

 调优总结

  1. 年轻代大小选择
    • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  2. 年老代大小选择
    • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
      • 并发垃圾收集信息
      • 持久代并发收集次数
      • 传统GC信息
      • 花在年轻代和年老代回收上的时间比例
  3. 减少年轻代和年老代花费的时间,一般会提高应用的效率
  4. 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  5. 较小堆引起的碎片问题
    因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
    • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
    • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
  6. jvm的内存限制

 

 

 

你可能感兴趣的:(Java基础:JVM)