JVM体系结构

1  运行原理

 

2  JVM体系结构

2.1  JVM体系结构

装载(Class Loader)子系统

堆装载 .class 文件的内容到 Runtime data area 中的method area(方法区域)

执行引擎(Execution Engine)子系统

执行引擎也叫做解释器(Interpreter),负责解释命令,提交操作系统执行。

本地接口(Native Interface)组件

本地接口的作用是融合不同的编程语言为Java 所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native 的代码,它的具体做法是Native Method Stack 中登记native 方法,在Execution Engine 执行时加载native libraries。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java 程序驱动打印机,或者Java 系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket 通信,也可以使用Web Service 等等。

运行数据域(Runtime Data Area组件

运行数据区是整个JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java 生态系统如此的繁荣,得益于该区域的优良自治

 

2.2  JVM Runtime Data Area


 

Method Area(方法区)

类加载子系统负载从文件或者网络中加载class信息,加载的类信息被保存在方法区的内存空间中,除了类信息外还会保存常量池信息。JDK8之前方法区的实现是被称为一种“永久代”的区域,这部分区域使用JVM内存,但是JDK8的时候便移除了“永久代(Per Gen)”,转而使用“元空间(MetaSpace)”的实现,而且很大的不同就是元空间不在共用JVM内存,而是使用的系统内存。

Heap(堆)

堆区是JVM中最大一块内存区域,存储着各类生成的对象、数组等。这块区域也是线程共享的。堆区被细化可以分为年轻代、老年代,而年轻代又可分为Eden区、From SurvivorTo Survivor三个区域,堆内存的大小是可以调节的,也是 gc 主要的回收区。

VM Stack(虚拟机栈):

虚拟机栈也叫栈内存,描述的是Java方法执行的内存模型,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over。栈用于存储栈帧信息、局部变量表、方法出口等信息。

Native Method Stack(本地方法栈)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务,完成与操作系统的交互。

PC Register(程序计数器)

是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 

 

2.3  JVM 堆内存

JVM内存分代策略

JVM根据对象存活的周期不同,把堆化成年轻代(Young Generation Space)、年老代(Tenured Generation Space

为什么要分代

堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,我们程序所有的对象实例都存放在堆内存中。给堆内存分代是为了提高对象内存分配和垃圾回收的效率。试想一下,如果堆内存没有区域划分,所有的新创建的对象和生命周期很长的对象放在一起,随着程序的执行,堆内存需要频繁进行垃圾收集,而每次回收都要遍历所有的对象,遍历这些对象所花费的时间代价是巨大的,会严重影响我们的GC效率。

Young Generation Space 年轻代:

所有新生成的对象优先存放在新生代中(大对象除外,大对象直接存放在年老代),新生代对象朝生夕死,存活率很低,在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95% 的空间,回收效率很高。

年轻代又分为两部分:伊甸区(Eden space)和幸存者区(Survivor pace),所有的类都是在伊甸区被new 出来的。幸存区有两个: 0 区(Survivor 0 space)和1 区(Survivor 1 space)。

当伊甸园的空间用完时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收,将伊甸园区中的不再被其他对象所引用的对象进行销毁。仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GCTo Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。

针对年轻代的垃圾回收即Minor GCYoung GC

Tenured Generation Space 年老代:

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

针对年老代的垃圾回收即Major GCFull GC

Full GC会对年轻代、年老代和持久代一起GC且此时应用无法响应用户请求,所以说Full GC回收成本高,应用应该避免频繁Full GC

 

3  垃圾回收算法及收集器

3.1  JVM 垃圾回收算法

3.1.1  引用计数算法(Reference Counting

比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时只回收计数为0的对象。此方法最致命的是无法处理循环引用的问题

3.1.2  复制(Copying)收集算法

将可用内存划分为相等的两块,每次只使用其中的一块。垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另外一个区域中。此算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现内存碎片问题。当然,此算法缺点也比较明显,需要两倍内存空间。复制收集算法在对象存活率高的时候,效率有所下降。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

3.1.3  标记-清除算法(Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时会产生内存碎片

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

此算法结合了“标记-清除”和“复制”两个算法的优点,也是分两个阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题。同时也避免了“复制”算法的空间问题。

3.1.5  分代收集算法(Generational Collection)

分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为年老代(Tenured/Old Generation)和新生代(Young Generation)。年老代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。新生代采用复制算法,年老代采用标记-整理算法。

3.2  JVM 垃圾收集器

3.2.1  Serial/ Serial old-串行收集器

最古老的收集器,是一个单线程收集器,用它进行垃圾回收时,必须暂停所有用户线程。
Serial
是针对新生代的收集器,采用Copying算法;
Serial Old
是针对老生代的收集器,采用Mark-Compact算法。
优点是简单高效,缺点是需要暂停用户线程。

3.2.2  Parnew-串行收集器(多线程)

Serial的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与serial收集器基本一致。
默认新生代垃圾收集器,和
CMS收集器配合使用。

3.2.3  Parallel Scavenge-并行收集器

新生代收集器,采用Copying算法,是并行的多线程收集器。看上去和ParNew都一样,那它有什么特别之处?

Parallel Scavenge收集器的特点是它关注点与其他收集器不同,CMS等收集器的关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控的吞吐量(Throughput.所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。

-XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间(毫秒)

-XX:GCTimeRatio:直接设置吞吐量大小,参数值为大于0小于100,的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1 /1+19)),默认值为99,就是允许最大1%(即1 /1+99))的垃圾收集时间

-XX:+UseAdaptiveSizePolicy :是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、EdenSurvivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

3.2.4  Parallel Old-并行收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,采用多线程和”标记-整理”算法。
吞吐量优先

3.2.5  CMS(Concurrent Mark Swep) -并发收集器

CMS一种获取最短回收停顿时间为目标的收集器,这使得它很适合用于和用户交互的业务,现在应用非常广泛。从名字(Mark Swep)就可以看出,CMS收集器是基于标记清除算法实现的。

它的收集过程分为四个步骤:
①初始标记
(initial mark):标记一下GC Roots能关联到的对象。会导致stop the world
②并发标记
(concurrent mark) GC Roots Tracing,从“初始标记”阶段标记的对象开始找出所有存活的对象。与用户线程同时运行
③重新标记(remark):完成标记整个年老代的所有的存活对象。会导致stop the world
④并发清除
(concurrent sweep):清除那些没有标记的对象并且回收空间。与用户线程同时运行

在整个过程和中最耗时的并发标记并发清除过程收集器程序都可以和用户线程一起工作,所以总体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的,所以说CMS是一个真正意义上的并发收集器。

由于CMS收集器是基于标记清除算法实现的,会导致有大量的空间碎片产生,在为大对象分配内存的时候,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前开启一次Full GC。为了解决这个问题,CMS收集器默认提供了一个-XX:+UseCMSCompactAtFullCollection收集开关参数(默认就是开启的),用于在CMS收集器进行FullGC完开启内存碎片的合并整理过程。

3.2.6  G1收集器

G1(Garbage First)收集器技术的前沿成果,是面向服务端的收集器,能充分利用CPU和多核环境。是一款并行与并发收集器,它能够建立可预测的停顿时间模型。主要有以下特点:

并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。
分代收集:和其他收集器一样,分代的概念在
G1中依然存在,不过G1不需要其他的垃圾回收器的配合就可以独自管理整个GC堆。
空间整合:
G1收集器有利于程序长时间运行,分配大对象时不会无法得到连续的空间而提前触发一次GC。
可预测的非停顿:这是
G1相对于CMS的另一大优势,降低停顿时间是G1CMS共同的关注点,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒

G1运作步骤:

1、初始标记;2、并发标记;3、最终标记;4、筛选回收

 

4  监控工具

4.1  代码级监控分析JVM 监控工具- jstat

JMX配置:tomcat的配置文件catalina.sh中增加:

JAVA_OPTS="$JAVA_OPTS \
-
Dcom.sun.management.jmxremote.port=9998 \
-
Dcom.sun.management.jmxremote.ssl=false \
-
Djava.rmi.server.hostname=192.168.xx.xx"

也可以在setenv.sh文件中增加,然后将setenv.sh文件放在和catalina.sh相同的目录下,setenv.sh内容如下,自己的环境需要修改IP和端口

JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 \
-server -Xms512m -Xmx512m -Xss1m \
-XX:NewSize=128m -XX:MaxNewSize=128m \
-XX:NewRatio=4 -XX:SurvivorRatio=4 \
-XX:+AggressiveOpts -XX:+UseBiasedLocking \
-XX:+UseConcMarkSweepGC -XX:ParallelCMSThreads=2 \
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/data/log/tomcat/gc.log \
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/tomcat/heapdump.bin \
-XX:+CMSParallelRemarkEnabled -XX:+ScavengeBeforeFullGC \
-XX:CMSInitiatingOccupancyFraction=75"

CATALINA_OUT=/data/log/tomcat/catalina.out

CATALINA_OPTS="-Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.port=10827 \
-Djava.rmi.server.hostname=192.168.231.130"

代码级监控分析JVM 监控工具- jvisualvm
Jconsolejmcjvisualvmjdk自带的监控工具,jdk/bin目录下

 

 监控VisualGC需要先启动./jstatd

cd /usr/local/jdk1.8/bin/
./jstatd -J-Djava.security.policy=all.policy &
./jstatd -J-Djava.security.policy=all.policy -p 端口号 &

 

4.2  代码级监控分析 

 

 

 

1、新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2、
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3、
运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4、
阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
   
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态
    (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、
死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

jstack Dump 日志文件中的线程状态
dump 文件里,值得关注的线程状态有:

死锁,Deadlock(重点关注)
执行中,Runnable  
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING

阻塞,Blocked(重点关注)
停止,Parked

Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。
Waiting on condition:等待资源,或等待某个条件的发生。具体原因需结合 stacktrace来分析。
Blocked:线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。
Waiting for monitor entry :Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。

 

5  监控命令

5.1  代码级监控分析JVM 监控工具- jstat

JstatJDK自带的一个轻量级小工具,功能特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控

命令:jstat - gcutil PID
示例:
jstat -gcutil 5047 2000
含义:每
2秒统计一次进程5047gc情况
主要关注:
FGCfull gc次数)、FGCT full gc 耗时)、GCTYGC+FGC总体耗时)

jstat -class pid:显示加载class的数量,及所占空间等信息。 
jstat -gc pid:可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。  
jstat -gcnew pid:new对象的信息。 
jstat -gcnewcapacity pid:new对象的信息及其占用量。 
jstat -gcold pid:old对象的信息。
jstat
-gcoldcapacity pid:old对象的信息及其占用量。

[root@192 WEB-INF]# jstat -gcutil 2732 1000
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00   0.00  78.02   0.00  60.98      0    0.000     0    0.000    0.000
  0.00  85.27  31.61   0.01  64.28      1    0.027     0    0.000    0.027
  0.00  85.27  55.53   0.01  87.32      1    0.027     0    0.000    0.027
  0.00  85.27  67.59   0.01  87.70      1    0.027     0    0.000    0.027
  0.00  85.27  85.99   0.01  88.53      1    0.027     0    0.000    0.027
 66.92   0.00   8.73   0.01  98.58      2    0.056     0    0.000    0.056
 66.92   0.00  22.34   0.01  98.85      2    0.056     0    0.000    0.056
 66.92   0.00  33.70   0.01  99.11      2    0.056     0    0.000    0.056
 66.92   0.00  45.14   0.01  99.24      2    0.056     0    0.000    0.056
 66.92   0.00  57.50   0.01  99.31      2    0.056     0    0.000    0.056
 66.92   0.00  95.50   0.01  99.44      2    0.056     0    0.000    0.056
 99.99   0.00   0.00  51.57  99.65      4    0.296     0    0.000    0.296
  0.00 100.00   0.00  81.06  99.82      5    0.437     1    0.000    0.437
  0.00   0.00  54.46  89.91  49.96      5    0.437     1    0.653    1.090
  0.00   0.00 100.00  89.91  49.99      5    0.437     2    0.653    1.090
  0.00   0.00  99.67  99.98  44.41      5    0.437     3    1.808    2.245
  0.00   0.00  97.02  99.98  46.16      5    0.437     4    2.532    2.969
  0.00   0.00 100.00  99.98  48.31      5    0.437     5    3.351    3.788
  0.00   0.00 100.00  99.98  50.66      5    0.437     6    4.190    4.627
  0.00   0.00 100.00  99.98  53.27      5    0.437     7    5.064    5.501
  0.00   0.00 100.00  99.98  56.15      5    0.437     8    5.922    6.358
  0.00   0.00 100.00  99.98  59.36      5    0.437     9    6.787    7.224
  0.00   0.00 100.00  99.98  62.48      5    0.437    10    7.655    8.092
  0.00   0.00 100.00  99.98  65.95      5    0.437    11    8.511    8.948
  0.00   0.00 100.00  99.98  68.68      5    0.437    12    9.375    9.812
  0.00   0.00 100.00  99.98  72.26      5    0.437    13   10.230   10.667
  0.00   0.00 100.00  99.98  77.66      5    0.437    15   11.944   12.381
  0.00   0.00 100.00  99.98  80.68      5    0.437    16   12.812   13.249
  0.00   0.00 100.00  99.98  83.10      5    0.437    17   13.677   14.113
  0.00   0.00 100.00  99.98  85.67      5    0.437    18   14.528   14.965
  0.00   0.00 100.00  99.98  87.47      5    0.437    19   15.377   15.814
  0.00   0.00 100.00  99.98  89.36      5    0.437    20   16.230   16.667
  0.00   0.00 100.00  99.98  92.37      5    0.437    22   17.944   18.381
  0.00   0.00 100.00  99.98  93.42      5    0.437    23   18.812   19.249
  0.00   0.00 100.00  99.98  94.48      5    0.437    24   19.682   20.118
  0.00   0.00 100.00  99.98  94.48      5    0.437    25   20.535   20.972
  0.00   0.00 100.00  99.98  95.57      5    0.437    26   21.411   21.848
  0.00   0.00 100.00  99.98  96.68      5    0.437    27   22.278   22.715
  0.00   0.00 100.00  99.98  97.81      5    0.437    29   23.999   24.436
  0.00   0.00 100.00  99.98  97.81      5    0.437    30   24.867   25.304
  0.00   0.00 100.00  99.98  97.81      5    0.437    31   25.723   26.160
  0.00   0.00 100.00  99.98  97.81      5    0.437    32   26.581   27.017
  0.00   0.00 100.00  99.98  98.98      5    0.437    33   27.428   27.865
  0.00   0.00 100.00  99.98  98.98      5    0.437    34   28.284   28.721

5.2  代码级监控分析JVM 监控工具- jstack

jstackJDK自带的一个轻量级小工具,主要用于打印出给定的Java进程IDcore file或远程调试服务的Java堆栈信息。
语法:
jstack  pid(线程号)   关注点:线程状态、调用栈

5.3  代码级监控分析JVM 监控工具- jmap

JmapJDK自带的一个轻量级小工具,可以输出所有内存中对象,甚至可以将VM 中的heap,以二进制输出成文本。打印出某个Java进程(使用pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)。

常用参数:
jmap
-heap pid:打印堆内存使用情况
jmap
-histo:live pid:打印每个class的实例数目,内存占用,类全名信息
jmap
-dump:live,format=b,file=111.hprof pid
使用
hprof二进制形式,输出jvmheap的活跃对象内容到文件,可以down到本地使用其他内存分析工具分析

分析过程如下:

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值