java虚拟机学习总结

时间:2022-12-28 08:27:16

前言

  • 最近流行一句话,我们须要明白我们从哪里来,要到哪里去。我学习使用jvm也是一样,不但会使用,也需要深入jvm原理,才能更好利用它。
  • 了解jvm运行的内存分配。与为什么jvm会抛出OutOfMemoryError。
  • 需要熟悉GC回收算法,目前有的垃圾回收器。是我们优化jvm的的前提。
  • 学习jvm让我们更加深入了解java程序的运行,也提高自己在解决问题能力。也明白在app生产运维我们如何监控jvm的各项参数。
  • 掌握jvm是程序开发人员,压力测试人员,运维人员必备知识。

java内存分配

堆(heap)

java虚拟机学习总结

  1. java的堆在jdk1.8以前被分为三个代,新生代(young generation),老年代(old generation),持久代(permanent generation)。新生代又被进一步划分为Eden(伊甸)和Survivor(幸存者)区,最后Survivor由From Space和To Space组成,结构图如上所示。

  2. 新生代:新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor(幸存者)中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。

  3. 旧生代:用于存放新生代中经过多次垃圾回收仍然存活的对象。
  4. 持久代: 用于存放静态文件,如今 Java 类、方法等。持久代对垃圾回收没有显著影响, 但是有些应用可能动态生成或者调用一些 class,例如 Hibernate 等, 在这种 时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久 代大小通过-XX:MaxPermSize=进行设置。

  5. 通过如下图:我们来了解heap分配与设置

java虚拟机学习总结

  • ◦-Xms:初始堆大小
  • ◦-Xmx:最大堆大小
  • -XX:Xmn :设置年轻代大小
  • ◦-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • ◦-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor(幸存者)区有两个。如:3,表示Eden:Survivor=3:1,一个Survivor区占整个年轻代的1/4
  • ◦-XX:MaxPermSize=n:设置持久代大小
  • 通过上面参数与比例图大家应该明白heap比例分配,MaxPermSize指定持久代的大小,然后通过NewRatio比例来分配新生代与老年代的的大小。

    1. heap内存溢出OutOfMemoryError,会进一步提示java heap space.

    2. 对于持久代的OOM问题,方法区存放Class的信息,例如类名,访问修饰符,产量池,字段描述,方法描述。对于这区OOM测试,就是运行时产生大量的类去填满方法区,直到溢出。1.利用String.intern()不断添加产量池。2.还有不断添加Class信息,利用cglib。这PermGen异常也要引起注意。

栈(stack)

1.每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。它是每个线程私有的内存空间,生命跟线程周期一样。-Xss来指定stack的大小,

2.如果线程的请求stack的大小超过了-Xss允许的最大size,则会抛出*Error。

3.如果jvm在扩展栈时无法申请到足够的内存空间,则会抛出OutOfMemoryError。通过设置了-Xss=5M,不断穿件线程则会抛出该异常。

4.可创建线程数=(操作系统进程允许最大内存-堆内存-方法区内存)/单个线程大小

本地方法栈

1.用于支持native方法的执行,存储了每个native方法调用的状态。会抛出*Error与OutOfMemoryError。

2.抛出*Error与OutOfMemoryError情况与栈stack。

3.采用一个死递归方法调用,就重现*Error。

直接内存(direct memory)

1.它不是虚拟机运行时数据区的一部分,也不是jvm规范的一部分。但它也被频繁地使用。也可能导致OutOfMemoryError。

2.jdk1.4以后加入NIO,引入了一种基于channel与Buffer的IO方式。通过native方法直接分配内存,然后通过java堆中DirectByteBuffer对象来引用。

  1. -xx:MaxDirectMemorySize指定,如果不指定,则默认javaHeap大小一致。

垃圾收集算法

复制算法

  1. 将新生代分为eden与幸存者区,每次gc的时候把一直还存活的Object复制到另外一块幸存者区上面,然后清空eden与另外一个快幸存者区。这样手机算法不需要考虑内存碎片问题,简单高效。但是当幸存对象大于幸存者区空间的时候,我们需要内存分配担保机制,用老年代的一块连续的大空间来担保。
  2. 记住一个概率内存分配担保机制。

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

  1. 主要用于老年代回收,意思是先标记,让存活的对象向一边移动,然后直接清理另外一端。

垃圾收集器

java虚拟机学习总结

  1. 如果说垃圾收集算法是内存回收的方法论,那么垃圾回收器则是具体实现。如图介绍,年青代与老年代不同垃圾收集器的配合关系。

  2. StopTheWorld概念:指大多数垃圾收集器在工作时候,需要停止服务处理。如果是FullGC则停止服务处理时间比较长,所有我们需要优化jvm配置,减少停顿时间。

  3. 串性收集器(Serial 收集器):它会暂停其它所有的线程工作,直到它收集结束。stop the world,已近不怎么用了。

  4. ParNew收集器(parallel MSC):是Serial收集器的并发版本多线程模式,收集算法,对象分配,回收策略都一样。server模式下新生代的收集器首选,目前只老年代的cms收集器配合工作。使用cms后,默认就是ParNew来收集新生代。

  5. cms(ConcurrentMarkSweep):收集器是以获取最短回收停顿时间为目标的收集器。采用标记-清除算法。优点,并发收集,低停顿。 (它收集老年代时,新生代只能使用ParNew,Serial收集器)。非常适合互联网,BS系统。

  6. G1收集器:garbage first 当今最前沿的成果。jdk1.7 才被定义来商业。1.并行与并发,busiThread可以不停止,缩短stoptheworld的时间。它将覆盖整个heap

7.Serial old收集器,串行的old genration 收集器。
8.Parallel old收集器,是Parallel Scavenge收集器的老年代版本,多线程,标记-整理

2.收集器设置
◦-XX:+UseSerialGC:设置串行收集器
◦-XX:+UseParallelGC:设置并行收集器
◦-XX:+UseParalledlOldGC:设置并行年老代收集器
◦-XX:+UseConcMarkSweepGC:设置并发收集器

3.垃圾回收统计信息
◦-XX:+PrintGC
◦-XX:+PrintGCDetails
◦-XX:+PrintGCTimeStamps
◦-Xloggc:filename

4.并行收集器设置
◦-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
◦-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
◦-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

5.并发收集器设置 ◦-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
◦-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
work     11833  0.4  4.8 3774132 786640 ?      Sl   Mar15  11:29 /usr/bin/java -Djava.util.logging.config.file=/alidata/server/bweb_mind/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms512M -Xmx1024M -X:PermSize=256M -XX:MaxPermSize=256M -XX:NewRatio=1 -XX:SurvivorRatio=8 -Xnoclassgc -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurentAndUnloadsClasses -XX:CMSFullGCsBeforeCompaction=5 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -Xloggc:/alidata/server/bweb_mind/logs/gc_mind.log -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC -Djdk.tls.ephemeralDHKeySize=2048 -Djava.enorsed.dirs=/alidata/server/bweb_mind/endorsed -classpath /alidata/server/bweb_mind/bin/bootstrap.jar:/alidata/server/bweb_mind/bin/tomcat-juli.jar -Dcatalina.base=/alidata/server/bweb_mind -Dcatalina.home=/alidata/server/bweb_mind -Djava.io.tmpdir=/alidat/server/bweb_mind/temp org.apache.catalina.startup.Bootstrap start

结合上面配置我们解释下:
permanent持久代Max256M
新生代Max512M
edem: 8/9
survivor: 1/9
老年代Max512M
UseParNewGC: 使用ParNew+SerialOld 的收集器组合进行新生代内存回收
-XX:+UseConcMarkSweepGC :使用ParNew+cms+senal Old的收集器,老年代
+PrintGCDetails:在垃圾回收的时间打印内存回收日志,并且进程退出时候打印各个区域的的情况。

FullGc的危害

  1. 对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,引起stopTheworld时间过长,导致Full GC一般由于以下几种情况:
  2. 旧生代空间不足
    调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象
  3. Pemanet Generation 方法区空间不足
    增大Perm Gen空间,避免太多静态对象
  4. 统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
    控制好新生代和旧生代的比例
  5. System.gc()被显示调用
    垃圾回收不要手动触发,尽量依靠JVM自身的机制
    调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果

jdk的监控与可视化工具

更多详细讲解,参考《深入理解Java虚拟机 JVM高级特性与最佳实践》,只需要深入研究第二部分。