JVM 详细介绍

一、JVM简介

         jvm英文全称: Java Virtual Mathine(java虚拟机)

         jvm世界观:java对象在jvm里的生老病死

         jvm一样也是通过在实际的计算软件虚拟出来,用来模拟一套完整的运行平台

        作用:是作为java编译器和不同平台之间的解释器,jvm根据不同的平台,将java编译后的目标代码(字节码)解释成不同平台可以运行的机器指令

二、JVM发展史

 1、Sun classic虚拟机

1)世界上第一款商用Java虚拟机

2)只能使用纯接收器方式来执行java代码

3)java语言很慢

 

2、 Exact VM虚拟机

1)JDK 1.2时,出现,支持编译器和解释器混合工作模式

2)它使用准确式内存管理

 

3、Sun HotSpot VM

1)hostspot是sun jdk和open jdk中所带的虚拟机,也是目前使用范围最广的kjava虚拟机

2)继承了sun之前两款商用虚拟机的优点,也有许多自己新的技术优势,如热点代码探测技术。

注意: 热点代码探测技术

1.可以通过执行计数器找到具有编译价值的代码

如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。

OSR:由于代码块可能是在代码块在解释执行过程中直接切换到本地代码执行,所以也叫做栈上替换

2.编译器与解释器恰当协同工作

三、JVM内存区------运行时数据区

1、运行时数据区域分为两种:

1.线性隔离数据区

            包括:程序计数器、java虚拟机栈、本地方法栈

2.线程共享的数据区

             包括:java堆、方法区

2、JVM内存区的介绍

1.程序计数器

1) 程序计数器就是当前线程所执行的字节码的行号指示器。

2) 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。

3)如果线程正在执行的是Native方法,这个计数器的值为空

4)异常:没有指定任何OutOfMemoryError情况

2.栈

1)简介

执行代码的内存区域,比如执行方法、变量、引用

2)栈里放了编译器可知的各种基本类型(boolean,byte,char,sort,int,float,long,double)、对象引用和指向了一条字节码指定的地址

3)分类

java虚拟机栈:

为程序员编写的java代码来服务,就是我们通常所说的堆栈中的栈。

本地方法栈:

用来执行被Native关键字修饰的代码的栈结构

4)异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,如果扩展时无法申请足够的内存,就会抛出OutOfMemoryError异常。

 

3、线程数据共享区域

1)java堆内存

java堆(java Heap)是虚拟机所管理的内存中最大的一块,此内存区域就是存放对象实例,几乎所有对象实例都在这里分配内存。

java堆是垃圾收集管理的主要区域:内存回收的角度来看java堆中还可以细分为:新生代和老年代;新生代细致一点的有Eden空间、From Survivor/oTo Survivor空间

在实现时,既可以实现成固定大小的,也可以说是可扩展的,不过当前主流的虚拟机丢失按照可扩展的(通过-Xmx设置最大内存和-Xms设置初始内存)

异常:如果在堆中没有内存完成实例分配,并且堆也无法再扩展时, 报错后dump出信息: -XX:+HeapDumpOnOutOfMemoryError

2)Java方法区

方法区又叫静态区:用于存储已被虚拟机加载的类信、常量池、静态变量、即时编译器编译后的代码等数据。虽然java虚拟机规范吧方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆)

对于Hotspot虚拟机是使用永久带来实现方法区;

Java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取出来一个(如果池中没有则创建一个),在需要重复创建相等变量时节省了很多时间。

异常:当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

四、JVM垃圾回收

1、对象的引用

java中的引用分为:强引用、软引用、弱引用、虚引用,这4种引用强度依次逐渐减弱

强引用:在程序代码之中正常的类似于“Person p = new Person()”这类的引用;垃圾收集器不会回收掉被强引用的对象。

软引用:有用但非必须的对象,jdk中提供了SoftReference类来实现软引用;系统在发生内存溢出异常之前,会把只被软引用的对象进行回收。用途?可以做缓存。

弱引用:非必须的对象,jdk中提供了WeakReference类来实现软引用,比软引用弱一些;垃圾回收不论内存是否不足都会回收只被弱引用关联的对象。

虚引用:对被引用对象的生存时间不影响;无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知;jdk提供

2.怎么判断对象的存活方式?

判断对象的存活方式:引用计数算法、可达性分析,主流的商用程序语言的主流实现中是通过可达性分析;

1)引用计数算法基本思想:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效是,计数器就减1,任何时刻计数器为0的对象是不可能再被引用的

2)可达性分析的基本思想:通过一系列的称为‘GC Roots’的对象作为起点 ,从这些 节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达时),则证明对象是不可用的。

GC Roots对象:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI(Java Native Interface即一般说的Native方法)引用的对象。

对象的生死

不可达的对象真正死亡需要两次标记:

当不可达时标记第一次标记,当对象覆盖finalize()方法并且finalize()方法没有被虚拟机调用过,此对 象将会放置在一个叫做F-Queue的队列之中,稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去触发这个方法,但并不承诺会等待它运行结束再执行垃圾回收。

finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中重新与引用链上的任何一个对象建立关联那么他被移除出“即将回收”的集合,否则就被回收了。

3、垃圾回收算法

1)标记-清除算法

最基础的收集算法是“标记-清除”(Mark-Sweep)算法,此方法分为两个阶段:标记、清除。

标记要清除的对象,统一清除;

不足有两个:

一个是效率问题,标记和清除两个过程的效率都不高;

另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

标记+清除+整理=标记整理算法

2)标记-整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

3)复制算法

    复制算法:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

优点:无内存碎片,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。缺点:实际可用内存缩小为了原来的一半。

现在的商业虚拟机都采用这种收集算法来回收新生代。

1、将内存分为一块较大的Eden空间和两块较小的Survivor空间;

2、每次使用Eden和其中一块Survivor。

3、当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,并清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1:1;浪费10%。

4、垃圾回收的方式

inor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。每次 Minor GC 会清理年轻代的内存。

Major GC: 是清理老年代或者永久代。

Full GC:是清理整个堆空间—包括年轻代和老年代或者永久代。

 

5、内存分配策略

      对象在Eden分配:大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,此时对象会进入survivor区,当对象满足一些条件后会进入老年代。

三种方式进入老年代:

  • 1.长期存活的对象将进入老年代:虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
  • 2.如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
  • 3.大对象直接进入老年代:虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。

垃圾回收的对象空间担保问题:

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代的所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。

6.垃圾收集器

 收集器就是内存回收的具体实现。

java虚拟机规范没有对收集器应该如何实现有任何规定,因为不同版本、不同厂商的虚拟机提供的垃圾收集器都可能会有很大的差异。

并行:指多条垃圾收集线程并行工作,但是此时:用户线程仍然处于线程等待状态

并发:指用户线程和垃圾收集线程同时执行,用户程序继续运行,而垃圾收集程序运行在另一个cpu上。

7.垃圾收集器

1.Serial收集器

串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。新生代、老年代使用串行回收;新生代复制算法、老年代标记-压缩;垃圾收集

的过程中会 Stop The World(服务暂停)

参数控制:-XX:+UseSerialGC 串行收集器

2.ParNew收集器

ParNew 收集器其实就是 Serial 收集器的多线程版本。

新生代并行,老年代串行;新生代复制算法、老年代标记-整理

参数控制:-XX:+UseParNewGC ParNew 收集器

3、Parallel收集器

Parallel Scavenge 收集器类似 ParNew 收集器,Para llel 收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制 GC 的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-整理算法

参数控制:

-XX:+UseParallelGC 使用 Parallel 收集器 + 老年代串行

4、CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。

从名字(包含“Mark Sweep”)上就可以看出 CMS 收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为 4 个步骤,包括:

1、初始标记(CMS initial mark)

2、并发标记(CMS concurrent mark)

3、重新标记(CMS remark)

4、并发清除(CMS concurrent sweep)

参数控制:

-XX:+UseConcMarkSweepGC 使用 CMS 收集器

5.G1收集器(重要)

G1 是目前技术发展的最前沿成果之一,HotSpot 开发团队赋予它的使命是未来可以替换掉JDK1.5 中发布的 CMS 收集器。

特点:

1、并行与并发:利用多cpu缩短stop-the-world的时间,使用并发方式解决其它收集器需要停顿的gc动作。

2、分代收集:新老代收集区分对待。

3、空间整合:G1从整理看是基于标记-整理,但是局部看是基于复制算法实现的,不会产生碎片。

4、可预测的停顿:能够让使用者指定在M毫秒的时间片段上,消耗在垃圾回收的时间不得超过N毫秒。

8.JVM分析工具

1.jps

jps:Java Virtual Machine Process Status Tool

http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jps.html

jps [ options ] [ hostid ]

-q 只显示pid,不显示class名称,jar文件名和传递给main 方法的参数

-m -m 输出传递给main 方法的参数,在嵌入式jvm上可能是null

-l 输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名

-v 输出传递给JVM的参数

jps host :查看host的jps情况(前提:host提供jstatd服务)

2.jstatd

jstatd:Virtual Machine jstat Daemon

http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstatd.html

jstatd [ options ]

启动jvm监控服务。它是一个基于rmi(远程接口调用)的应用,向远程机器提供本机jvm应用程序的信息。默认端口1099。-p指定端口。

实例:jstatd -J-Djava.security.policy=my.policy &

my.policy文件需要自己建立,内如如下:

grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };

这是安全策略文件,因为jdk对jvm做了jaas(Java验证和授权API)的安全检测,所以我们必须设置一些策略,使得jstatd被允许作网络操作;

3.jmap

Memory Map 观察运行中的jvm物理内存的占用情况。

官方地址:http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jmap.html

jmap [ option ] pid

pid 进程号(常用)

参数如下:

-heap:打印jvm heap的情况(垃圾收集器类型)

-histo:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。

-histo:live :同上,但是只打印存活对象的情况

-permstat:打印permanent generation heap(方法区)情况

-finalizerinfo:打印正等候回收的对象信息

4.jinfo

Configuration Info

官方地址:http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jinfo.html

jinfo [ option ] pid

pid 进程号

参数如下:

no option 打印命令行参数和系统属性

-flags 打印命令行参数

-sysprops 打印系统属性

-h 帮助

5.jstack

jstack:Stack Trace

http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html

jstack能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况

jstack [ option ] pid

[ option ] 参数如下

-l长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表.

-m打印java和native c/c++框架的所有栈信息.

五、jvm常用的优化参数

主要的参数是:堆的大小、栈的大小、新生代和老年代的比值、新生代中eden和s0、s1的比值。

-Xms:初始堆大小,默认是物理内存的1/64。默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到--Xmx的最大限制。例如:-Xms 20m。

-Xmx:最大堆大小。默认是物理内存的1/4 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。

-XX:NewSize=n:设置年轻代大小(初始值)。

-XX:MaxNewSize:设置年轻代最大值。

-XX:NewRatio=n:设置年轻代和年老代的比值。

-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。

-XX:PermSize(1.8之后改为MetaspaceSize) 设置持久代(perm gen)初始值,默认是物理内存的1/64。

-XX:MaxPermSize=n:(1.8之后改为MaxMetaspaceSize)设置最大持久代大小。

-Xss:每个线程的堆栈大小。

猜你喜欢

转载自blog.csdn.net/weixin_43823423/article/details/86314960
今日推荐