龙空技术网

一份详细介绍JVM的资料(对比JDK8和JDK7)

假装很有道理 302

前言:

此时同学们对“java8java7”大致比较注重,小伙伴们都想要学习一些“java8java7”的相关内容。那么小编在网摘上收集了一些对于“java8java7””的相关内容,希望姐妹们能喜欢,姐妹们一起来了解一下吧!

jvm组成

JVM是运行在操作系统之上的,它与硬件没有直接的交互 。

JVM 内存:

1 Class Loader 类加载器

2 Execution Engine 执行引擎 负责解释命令,提交操作系统执行。

3 Native Interface 本地接口

4 Runtime data area 运行数据区

JVM1.8JVM1.7示意图

Stack 栈 栈也叫栈内存,是 Java程序的运行区,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over。一些基本类型的变量和对象内存中分配的引用变量都是在函数的栈。

Heap堆 一个 JVM 实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行,堆内存分为三部分:

Permanent Space 永久存储区 (JDK8中,方法区)

Young Generation Space 新生区

Tenure generation space 养老区

堆内存示意图

JVMGC垃圾回收算法—》分代收集算法

默认的新生区(1/3)和养老区(2/3)

新生区 Eden 和S0 和S1 比例 (8:1:1)—》引用计数法(废弃),复制算法

养老区垃圾收集算法 —》标记清除算法 标记整理算法

1.8和1.7区别,1.8使用元数据区取代了永久代,就是JDK8没有了PermSize相关的参数配置了。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。

废除永久代的原因:

1、官方文档:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。

2、PermGen很难调整,PermGen中类的元数据信息在每次FullGC的时候可能被收集,但成绩很难令人满意。

3、而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class总数,常量池的大小,方法的大小等。

4、并且永久代内存经常不够用发生内存泄露。

jvm内存区永久代

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的 Class,Interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭 JVM 才会释放此区域所占用的内存。默认64M大小

如果出现java.lang.OutOfMemoryError: PermGen space,说明是Java虚拟机对永久代Perm内存设置不够。一般出现这种情况,都是程序启动需要加载大量的第三方jar包。例如:在一个Tomcat下部署了太多的应用。或者大量动态反射生成的类不断被加载,最终导致Perm区被占满。

(jdk8中,取消了永久代,其实jdk8中的元空间和jdk7的永久代都是方法区的实现)

新生代

新生区是类的诞生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。

新生区又分为两部分: 伊甸区(Eden space)和幸存者区(Survivor space) ,所有的类都是在伊甸区被new出来的。幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存 0区。再次GC的时候,将S0和Eden区的存活对象移动到S1区。(注意S0和S1只是相对的概念,每经历一次GC存活的对象年龄+1,经过若干次GC之后,年龄足够的对象变进入老年代)(轻GC,MIniGC)

如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二: (1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。 (2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

养老代

养老区用于保存从新生区筛选出来的 JAVA 对象,一般池对象都在这个区域活跃。(年龄默认15)(新生代和养老代统称堆)

Method Area方法区

方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。

PC Register

程序计数器 每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令

Native Method Stack

本地方法栈 它的具体做法是 Native Method Stack中登记native方法,在Execution Engine 执行时加载native libraies。

java内存区域

javaSe 7中规定的,java虚拟机所管理的内存包括如下几个运行时数据区域。

程序计数器:program Counter Register ,一块较小的内存空间,当前线程所执行的字节码的信号指示器。

java虚拟机栈:java Virtual Machine Stacks ,也是线程私有的,它的生命周期与线程相同。它描述的是java方法执行的内存模型,即每一个方法在执行的同时都会创建一个栈帧(stack frame)用于存储局部变量,操作数栈,动态链接,方法出口等信息。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈。

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用类似,虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机使用到的Native方法服务。

java堆(java Heap)是jvm所管理的内存中最大的一块。也是被所有线程共享的内存区域,在jvm启动时创建。几乎所有的对象实例以及数组都要在堆上分配。(又称GC堆,Garbage Collected heap),java堆还可以细分新生代和老年代。

方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息。

运行时常量池(Runtime Constant Pool)是方法区的一部分。

『运行时常量池对于class文件常量池来说另一个重要的特征就是具备动态性,java语言并不要求常量一定只是编译期间才能产生,运行期间也可以将新的常量放入池中,这种特性被开发人员利用的比较多的便是string类的intern()方法。』

如何判断对象已死?

引用计数算法(Reference Counting)

可达性分析(Reachablility Analysis)来判定对象是否存活。以一些列的GC Roots的对象作为起始点,从这些节点搜索所走过的路径称为引用链。

垃圾收集算法

1,标记-清除(mark-Sweep)算法,产生大量不连续内存碎片

2,复制算法(Copying),『现在商用的虚拟机大多采用这种算法,只是新生代中的对象是朝生夕死,IBM将此内存空间划分为一块Eden和俩Survivor』

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

4,分代收集算法,在不同的内存空间采用不同的算法。

三种算法比较

1、内存效率

复制算法 〉 标记清除算法 〉标记整理算法

2、内存整齐度

复制算法 = 标记整理算法 〉标记清除算法

3、内存利用率

标记清除算法 = 标记整理算法 〉 复制算法

耗时短 ,效率高 java9 G1垃圾回收器

GC root

GcRoot是垃圾回收器算法中判断一个对象是否可以回收的一种算法

就是对象到达GcRoot的路径是否还有可达,即是否有可引用链,如果有,这表明对象还存在着引用,

如果没有,则表明该对象没有引用,在下一次垃圾回收时就会被回收

GcRoot的种类

1.虚拟机栈:栈帧中的本地变量表引用的对象

2.native方法引用的对象

3.方法区中的静态变量和常量引用的对象

jvm参数调优

 #打印GC日志,包括各区情况 -XX:+PrintGCDetails     #打印GC简要信息 -XX:+printGC  #每一次GC前后打印堆信息 -XX:+PrintHeapAtGC  #监控类加载 -XX:+TraceClassLoading  #指定GC日志位置,一般GC的日志输出都在控制台,为了方便查看GC日志,可以指定位置。 -Xloggc:/home/zhao/log/gc.log  #堆的转存:在系统宕机内存溢出,我们很难排查出问题,这个时候我们可以做一个转存 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/zhao/java.dump  #使用串行GC器 -XX:+UseSerialGC  #设置java栈大小,一般没有必要,线程私有通常几百k。 -Xss #打印配置参数到控制台 -XX:+PrintCommandLineFlags  #如果我们只想看下所有XX参数的默认值,能够用一个相关的参数 -XX:+PrintFlagsInitial  #堆分配(设置最大值和最小值) #(最大堆大小) -Xmx20m #(初始化堆大小) -Xms5m #JVM性能调优:最关键参数: -Xms、 -Xmx 、-Xmn 、-XX:SurvivorRatio、-XX:MaxTenuringThreshold、-XX:PermSize、-XX:MaxPermSize #-Xms、 -Xmx 通常设置为相同的值,避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。 -Xmn #决定了新生代空间的大小,新生代Eden、S0、S1三个区域的比率可以通过-XX:SurvivorRatio=8来控制(假如值为 8 表示:Eden:S0:S1 = 8:1:1 ) 默认也是8 -XX:NewRatio=2# (新生代和年老代的比例,2表示新生代:老年代=1:2,默认也就是2)  -XX:MaxTenuringThreshold #控制对象在经过多少次minor GC之后进入老年代,此参数只有在Serial 串行GC时有效。  -XX:PermSize、-XX:MaxPermSize#用来控制方法区的大小,通常设置为相同的值。注意:PermSize在java8中已经丢弃。

mycat启动jvm参数说明

 JAVA_OPTS="-server -Xms2G -Xmx2G -XX:+AggressiveOpts -XX:MaxDirectMemorySize=2G"

其他一些的性能命令行选项

最新和最大优化

当新的性能优化集成到HotSpot VM中之后,可以通过如下选项来启用。

 -XX:+aggressiveopts

通过选项来引入新的优化,可以把最新及最大的优化和以及经过长时间使用证明是稳定的优化分离开。应用通常更希望获得更好的稳定性,毕竟最新的优化可能会导致未知的问题。但是如果应用需要提升任何可以提升的性能优化的时候,可以使用命令选项来启用这些优化。

逃避分析

逃避分析是一个种分析Java对象范围的技术,在特殊情况下,一个线程分配的对象可能被另外一个线程使用,这个对象就叫着“逃避”。如果对象没有逃避,额外的优化技术可以应用,因此,这种优化技术叫做逃避分析。

在HotSpot VM里面的逃避分析优化可以通过命令行选项

  -XX:+DoEscapeAnalysis
大页面

现代CPU引入了MMU(Memory Management Unit,内存管理单元)。MMU 的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由MMU负责将虚址映射为物理地址。在计算机系统中,内存被分为固定大小的区块,这个区块就叫做页(page)。

内存的存取是通过程序把虚拟内存地址转换成物理内存地址实现的。虚拟到物理地址是在一个块表里面映射的。为了减少每次存取内存的时候使用页表的消耗,通常会使用一种快速的虚拟到物理地址转换的缓存。这个缓存叫做页表寄存器缓冲(translation lookaside buffer),简称TLB

通过TLB来满足虚拟到物理地址的映射请求,会比遍历页表来找到映射关系快很多,一个TLB通常包含指定数量的条目。一个TLB条目是一个基于页大小虚拟到物理地址映射,因此,更大的页大小允许一个条目或者一个TLB有更大的内存地址范围。在TLB中有更广泛的地址,更少的地址转换请求在TLB中不命中,就可以减少遍历页表(page table)操作。使用大页的目的就是减少TLB的不命中。

oracle solariz,linux 以及windows都支持HotSpot VM使用大页。通常处理器可以支持几种页大小,不过不同的处理器各不相同。另外,操作系统配置需要使用大页。

下面说说怎么样在Linux下使用大页(Large Page)

调整OS和JVM内存分页

在Linux确认是否支持,请在终端敲如下命令:

 $cat /proc/meminfo | grep Huge  HugePages_Total: 0  HugePages_Free: 0  Hugepagesize: 2048 kB

如果有HugePage字样的输出内容,说明OS是支持大内存分页的。Hugepagesize就是默认的大内存页size。接下来,为了让JVM可以调整大内存页size,需要设置下OS 共享内存段最大值和大内存页数量。

共享内存段最大值,建议这个值大于Java Heap size,这个例子里设置了4G内存。

 $ echo 4294967295 > /proc/sys/kernel/shmmax

大内存页数量,这个值一般是 Java进程占用最大内存/单个页的大小,比如java设置1.5G,单个页10M,那么数量为1536/10 = 154。

 $ echo 154 > /proc/sys/vm/nr_hugepages

单个页大小调整,JVM启用时加参数

  -XX:LargePageSizeInBytes=10m

如果JDK是在1.5 update5以前的,还需要手动加如下配置,作用是启用大内存页支持。

  -XX:+UseLargePages
java支持的运行参数

Java支持的运行参数包括如下几种:

标准参数(-):所有的JVM实现都必须实现这些参数的功能,而且向后兼容;

非标准参数(-X):默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容;

非Stable参数(-XX):此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用;

-client 与-server

运行模式相关的,JVM工作在Server模式可以大大提高性能,但应用的启动会比client模式慢大概10%。当该参数不指定时,虚拟机启动检测主机是否为服务器,如果是,则以Server模式启动,否则以client模式启动。Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。

-cp

类和jar路径搜索,用:分割多目录和jar。

运行和调试

-verbose:class

程序启动时类加载情况,效果等同于-verbose,当JVM报告类找不到或者类冲突可以使用此参数诊断JVM装载类的情况。

-verbose:gc

当jvm发生gc时输出gc相关信息。

-verbose:jni

输出native方法调用的相关情况,用于诊断jni错误信息。

-ea<className>

开启断言,java断言默认是不开启的,关闭断言 -ea不指定参数,或者-da

设置系统变量参数

-D<key>=<value>

程序可以使用System.getProperty(‘key')获取」

-Xprof

调试监控相关, 输出性能分析数据

jvm调优与上线排查jps:虚拟机进程状况工具 jps -l #输出应用程序main class的完整package名或者应用程序的jar文件完整路径名,同时打印当前java进程id。

 ➜ ~ jps -l 36688 jdk.jcmd/sun.tools.jps.Jps 31619 org.jetbrains.idea.maven.server.RemoteMavenServer36 32810 org.jetbrains.jps.cmdline.Launcher 31483 32811 io.renren.AdminApplication 
jstat -option <pid>:虚拟机统计信息监视工具 stat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id。
 ➜ ~ jstat -class 32811 # 统计类加载 Loaded Bytes Unloaded Bytes     Time  17547 33697.7      361   533.1       8.26 ➜ ~ jstat -compiler 32811 #编译统计 Compiled Failed Invalid   Time   FailedType FailedMethod    14327      7       0     4.31          1 sun/reflect/annotation/TypeAnnotationParser buildAnnotatedTypes ➜ ~ jstat -gc  32811 #GC统计  S0C   S1C   S0U   S1U     EC       EU       OC         OU       MC     MU   CCSC   CCSU   YGC     YGCT   FGC   FGCT   CGC   CGCT     GCT 6144.0 7680.0 4336.1  0.0   596480.0 14765.7   282112.0   69923.5   102056.0 95932.4 12712.0 11637.8     58    0.434   4      0.584   -          -    1.019 ➜ ~ jstat -gccapacity  32811 # 堆内存统计  NGCMN   NGCMX     NGC     S0C   S1C       EC     OGCMN     OGCMX       OGC         OC       MCMN     MCMX     MC     CCSMN   CCSMX     CCSC   YGC   FGC   CGC  87040.0 1397760.0 611840.0 6144.0 7680.0 596480.0   175104.0  2796544.0   282112.0   282112.0      0.0 1138688.0 102056.0      0.0 1048576.0  12712.0     58     4     - ➜ ~ jstat -gcutil  32811 #总体GC统计  S0     S1     E     O     M     CCS   YGC     YGCT   FGC   FGCT   CGC   CGCT     GCT  70.57   0.00   4.21  24.79  94.00  91.55     58    0.434     4    0.584     -        -    1.019 
jinfo <pid>:java配置信息工具
 ➜ ~ jinfo 32811 Java System Properties: #Mon Jul 26 17:54:31 CST 2021 java.vendor=Oracle Corporation jboss.modules.system.pkgs=com.intellij.rt sun.java.launcher=SUN_STANDARD catalina.base=/private/var/folders/h7/d7yw88895b5cdh2hr25yrkk00000gn/T/tomcat.4533934885846294031.8892 sun.management.compiler=HotSpot 64-Bit Tiered Compilers sun.nio.ch.bugLevel= catalina.useNaming=false spring.output.ansi.enabled=always os.name=Mac OS X 
jhat:虚拟机堆转储快照分析工具jmap:内存映像工具jstack:java堆栈跟踪工具VisualVM:多合一故障处理工具jconsole: java自带的图形化内存管理工具Memory Analyzer Tool(MAT)Eclipse插件

标签: #java8java7