JVM(java 虚拟机)

生来就可爱ヽ(ⅴ<●) 提交于 2020-12-25 03:27:01

 

JVM(java 虚拟机)

一.JVM简介

1.JVM:Java Virtual Machine (java 虚拟机)

通过软件来模拟出来的具有完整的硬件系统功能、运行在完全隔离的环境中的完整的计算机系统。

2.种类:

1)Sun Classic 经典款
2)Exact VM 准确式内存管理
3)Sun HotSpot VM 热代码跟踪

二.JVM运行时的区域

1.运行时的区域

2.线程隔离区域(程序计数器、java栈)

程序计数器:是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,所以程序计数器这类内存区域为“线程私有的内
如果线程正在执行的是Native方法,这个计数器的值为空。

3.java 栈
执行代码的内存区域,比如执行方法,变量,引用等。包括:java虚拟机栈、本地方法栈;他们作用相似,区别只是:虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

栈里存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和指向了一条字节码指令的地址。

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
局部变量表所需的内存空间在编译期间完成分配,其中64位long和double类型的数据会占2个局部变量的空间,其余的数据类型只会占用一个。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

4.Java虚拟机栈和本地方法栈

Java虚拟机栈:为程序员编写的java代码来服务的,就是我们通常所说的堆栈中的栈。
本地方法栈:用来执行被native关键字修饰的代码的栈结构

5.线程数据共享区域(包括:Java堆内存,Java方法区)
Java堆内存:
Java堆是Java虚拟机所管理的内存中最大的一块,此内存区域就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域;从内存回收的角度来看可以细分为:新生代和老年代;
新生代细致一点有Eden空间,From Survivor 空间,To Survivor空间
在实现时,既可以实现成固定的大小,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx设置最大内存和-Xms设置初始内存)
Java 方法区
方法去又叫静态区:用于存储已被虚拟机加载的类信息,常量池、静态变量、即使编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-heap(非堆)
对于HotSpot虚拟机是使用永久代来实现方法区;
Java虚拟机规范对方法区的限制非常宽松,除了不需要连续的内存和可以选择固定大小或可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,条件相对苛刻。
Java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),在需要重复创建相等的变量时节省了很多时间。

6.异常:

1)程序计数器:没有指定任何OutOfMemoryError情况
2)栈:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常
3)堆:如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOf MemryError异常。错误后dump出信息:-XX:+HeapDumpOnOutOfMemoryError
4)方法区:当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常

三.JVM垃圾回收器

1.对象引用:Java中引用分为:

强引用:在程序代码之中正常的类似于“Person p = new Person()"这类引用;垃圾回收器不会回收掉被引用的对象
软引用:有用但非必须得对象,jdk中提供了SoftReference类来实现软引用;系统在发生内存溢出异常之前,会把只被软引用的对象进行回收。(用途:可以做缓存)
弱引用:非必须得对象,jdk 中提供了WeakReference类来实现软引用;比软引用弱一些;垃圾回收不论内存是否不足都会回收只被弱引用关联的对象。
虚引用:对被引用对象的生存时间不影响;无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联的唯一目的的就是能在这个对象被收集器回收时收到一个系统通知;jdk提供PhantomReference类来实现虚引用。
这四种引用强度依次逐渐减弱。
2.对象的可达性:
判断对象的存活方式:引用计数算法、可达性分析;主流的商用程序语言的主流实现中通过可达性分析;
引用计数法:
引用计数算法基本思想:给对象中添加一个引用的计数器每当有一个地方引用他时,计数器的值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
可达性分析:
1)基本思想:通过一些列的称为“GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用。
2)常见的GC roots对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象。(Person P = new Person());
方法中类静态属性引用的对象。(public ststic Persion p = new Persion());
方法区中常量引用的对象(public ststic final String str = “hello”?
本地方法栈中JNI(Java Native Interface 即一般说的Native方法)引用的对象。
public ststic native void sleep (long millis)
3.对象的生死
1)不可达的对象真正死亡需要两次标记:
2)当不可达时标记第一次标记,当对象覆盖finalize()方法并且finalize()方法没有被虚拟机调用过,此对象将会放置在一个叫做F-Queue的队列之中,稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去触发这个方法,但并不承诺会等待它运行结束再执行垃圾回收。
3)finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中重新与引用链上的任何一个对象建立关联那么他被移除出“即将回收”的集合,否则就被回收了。
@Override
protected void finalize() throws Throwable {
System.out.println(“finalized”);
}
4.标记-清除算法
1)最基础的收集算法是“标记-清除”(Mark-Sweep)算法,此方法分为两个阶段:标记、清除。
2)标记要清除的对象,统一清除;
3)不足有两个:
一个是效率问题,标记和清除两个过程的效率都不高;
另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
4)标记+清除+整理=标记整理算法
5.GC方式
GC方式分三种:
1)Minor GC:
从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。每次 Minor GC 会清理年轻代的内存。
2)Major GC:
Major GC 是清理老年代或者永久代。
3)Full GC:
Full GC 是清理整个堆空间—包括年轻代和老年代或者永久代。

四.内存分配策略

1.对象的晋升老年代的三种策略:
1)当对象的年龄达到(默认值)15之后(执行Minor GC,如果对象存活,年龄+1),就会从新生代(survivor)进入到老年代。(年龄阈值可以通过参数:-XX:MaxTenuringThreshold设置。
2).在Survivor中的相同年龄age的对象总大小大于了Survivor空间的一半。年龄大于等于该age的对象,集体晋升到老年代。
3).大对象直接进入到老年代,就是说,一个对象被所创建所需要的内存空间大小超过了:-XX:PretenureSizeThreshold(默认每值,也就是说所有的对象都是在新生代中进行创建)所表达的大小,该对象将会直接在老年代中进行创建,而不是新生代中创建。这样做的好处就是在Eden区及两个Survivor区之间发生大量的内存复制
2.垃圾回收的对象空间担保的问题
1)在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代的所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。
2)如果不成立,则虚拟机会查看-XX:HandlePromotionFailure设置是否允许担保失败。
3)如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC 是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为一次Full GC。

五.垃圾收集器

1.概念:
收集器就是内存回收的具体实现。
2.并行&并发
并行:指多条垃圾收集线程并行工作,但是此时:用户线程仍然处于线程等待状态。
并发:指用户线程和垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。
线程的分类:用户线程;守护线程
3.常见的垃圾收集器
1)Serial
Serial收集器是最基础、历史最悠久的适合新生代的收集器。
特点:单线程、stop-the-world 、复制算法
缺点:影响用户响应时间
优点:回收时简单高效、对于限定单个cpu环境下,serial收集器由于没有线程交互的开
销,专心做垃圾收集,可以获得最高的单线程收集效率。
所以:serial 收集器 对于运行在client模式下的虚拟机来说,是一个很好的选择。
SerialOld收集器是Serial的老年代收集器,采用“标记-整理”
2)ParNew
ParNew收集器其实是Serial的多线程版本,除了他是使用多条线程来进行垃圾回收之外
和Serial是完全一样的。新生代收集器
特点:多线程、stop-the-world
缺点:单个cpu下,运行效果甚至没Serial好。
优点:回收时简单高效、对于限定多个cpu环境下,效果比serial好。
所以:parnew收集器是运行在server模式下的首选收集器。
3)Parallel Scanvenge
Parallel Scanvenge收集器是一个新生代收集器,采用复制算法。
特点:收集新生代,复制算法,多线程,高吞吐、自适应
1、与其它的收集器侧重垃圾回收时用户的停顿时间不同,它主要侧重与吞吐量,
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
停顿时间越短就越适合需要与用户交互的程序,高吞吐量则是可以高效率地利用
cpu时间尽快完成任务。
2、他有一个自适应开关(-XX:+UseAdaptiveSizePolicy):打开后,用户只需要把基
本的内存数据(堆最大,初始量)设置好,然后设置更关注最大停顿时间或者更关
注吞吐量,收集器会把细节参数自动调节。
Parallel Old 老年代收集器,采用标记-整理算法
4)CMS
CMS(concurrent mark sweep)收集器是一个以获取最短回收停顿时间为目标的
老年代收集器。
特点:并发收集、低停顿。
基于 标记-清除算法实现,但是整个过程比较复杂一些。过程分为4步:
1、初始标记:仅仅标记GCRoot能直接关联到的对象。速度很快,“stop the world”
2、并发标记:GCRoot Tracing。耗时长和用户线程同步。
3、重新标记:修正并发标记时,由于用户程序运行导致的标记变动。“stop the
world”停顿稍长一些。
4、并发清除:耗时长,和用户线程同步。
缺点:吞吐量会变低、浮动垃圾无法处理、标记-清除的碎片(设置参数是 fullgc前
开启碎片整理功能,gc停顿时间延长)。
可以兼容的新生代收集器:ParNew和Serial
5)G1
G1(Garbage-First)收集器是当今收集器领域最前沿成果之一。2004年sun发表
第一篇G1论文,10年后才开发出G1的商用版本。
hotspot开发团队赋予它的使命:未来替调CMS收集器。
特点:
1、并行与并发:利用多cpu缩短stop-the-world的时间,使用并发方式解决其它收
集器需要停顿的gc动作。
2、分代收集:新老代收集区分对待。
3、空间整合:G1从整理看是基于标记-整理,但是局部看是基于复制算法实现的,
不会产生碎片。
4、可预测的停顿:能够让使用者指定在M毫秒的时间片段上,消耗在垃圾回收的时
间不得超过N毫秒。
过程:初始标记、并发标记、最终标记、筛选回放。前三个和CMS一致,筛选回放
是根据用户设置的停顿目标来选择回收价值最高的进行回收。

六.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:打印正等候回收的对象信息

用jmap把进程内存使用情况dump到文件中,再用jhat分析查看。jmap进行

dump命令格式如下: jmap -dump:format=b,file=dumpFileName pid jmap
-dump:format=b,file=4574.heap20151215 4574 Dumping heap to 4574.heap20151215 Heap dump file created

dump出来的文件可以用MAT、VisualVM等工具查看,这里用jhat查看: jhat -port 9998
/tmp/dump.dat

注意如果Dump文件太大,可能需要加上-J-Xmx512m这种参数指定最大堆内存,即jhat -J-Xmx512m -port 9998
/tmp/dump.dat。然后就可以在浏览器中输入主机地址:9998查看了

注意:-J-Xmx512m中间没有空格。
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++框架的所有栈信息.

tid指Java Thread
id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址

dump 文件里,值得关注的线程状态有:

死锁,Deadlock(重点关注)

等待资源,Waiting on condition(重点关注)

等待获取监视器,Waiting on monitor entry(重点关注)

阻塞,Blocked(重点关注)

执行中,Runnable

暂停,Suspended

对象等待中,Object.wait() 或 TIMED_WAITING

停止,Parked

6)jstat

jstat: Java Virtual Machine Statistics Monitoring Tool
http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html
Usage: jstat -help|-options jstat - [-t] [-h]
[<interva[s|ms]> []]
参数解释:
Options — 选项,我们一般使用 -gcutil /-gc 查看gc情况 pi
d — VM的进程号,即当前运行的java进程号
interval[s|ms] —— 间隔时间,单位为秒或者毫秒,默认为ms。必须是正整型。
count — 打印次数,如果缺省则打印无数次
例如:jstat -gc 4645 500 10 表示查看进程号为4645的gc 每500ms打印一次 共打印10次
S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:年轻代中Eden(伊甸园)的容量 (字节)
EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
PC:Perm(持久代)的容量 (字节)(jdk1.8以后叫做EC)
PU:Perm(持久代)目前已使用空间 (字节)(jdk1.8以后叫做EU)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
jstat -gccapacity pid
NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
NGCMX:年轻代(young)的最大容量 (字节)
NGC:年轻代(young)中当前的容量 (字节)
OGCMN:old代中初始化(最小)的大小 (字节)
OGCMX:old代的最大容量 (字节)
OGC:old代当前新生成的容量 (字节)
PGCMN:perm代中初始化(最小)的大小 (字节) (jdk1.8之后为MCMN)
PGCMX:perm代的最大容量 (字节) (jdk1.8之后为MCMX)
PGC:perm代当前新生成的容量 (字节)(jdk1.8之后为MC)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
TT: 持有次数限制
MTT : 最大持有次数限制
7)jconsole

可视化的jvm监控软件。
可以监控本地或者远程进程。
主要包括:概览、内存、线程、类、VM概要、MBean选项卡。
概览选项卡:呈现四幅图表:主要包括堆内存使用量、类、线程、CPU占有率。
内存选项卡:包含堆内、非堆、内存池的使用量图表和详细信息。相当于jstat命令。
线程选项卡:显示所有的线程的信息和图表。相当于jstack
类选项卡:加载的类的信息。
Vm概要:VM的概要信息包括堆大小,垃圾收集信息、vm参数等。
Mbean选项:managed beans ,被管理的beans
8)jvisual vm

多合一故障处理工具

visual vm是迄今为止,jdk发布的功能最强大的运行监视和故障处理程序。可以查看本地和远程的的状态。

优点:不需要被监控的程序基于特殊的Agent运行,因此他对应用程序的实际性能影响很小,这样他可以直接运行在生产环境中。这是其它监视工具比如:jprofile、yourkit无法与之相比的。

visualvm.exe

主要特点:

1、插件安装。 2、生成、浏览堆转储快照和线程快照。 3、抽样器和profiler中分析程序性能。 4、BTrace插件动态日志跟踪。

9)jmx
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。
开启jmx:
无需验证的配置:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.port=1234
-Dcom.sun.management.jmxremote.ssl=false
将-Dcom.sun.management.jmxremote.authenticate=false 去掉就是需要验证信息即:登录名和密码:验证用户的配置文件在JAVAHOME/jre/lib/management/jmxremote.password默认有一个JAVA_HOME/jre/lib/management/jmxremote.password默认有一个JAVA
H

OME/jre/lib/management/jmxremote.password默认有一个JAVA_HOME/jre/lib/management/jmxremote.password.template
修改下即可。
jmxremote.password.template默认是只读权限。
并更改为可写的权限 chmod a+w jmxremote.password
修改后把jmxremote.password的读权限取消 a-r 。否则会提示:Error: Password file read access must be restricted:
management/jmxremote.access配置下权限。
连接时指定ip:端口和jmx连接即可。
远程的线程dump可以dump查看,堆的dump是需要从远程拉到本地来查看。
10)jconsole和jvisualvm远程

jconsole:
在连接界面有显示本地连接或者远程连接,输入相应的主机名和jmx的端口号即可。
jvisualvm:
在远程的选项添加计算机,然后右键添加jmx连接或者jstatd连接。
七.jvm常用的优化参数
Java1.7的jvm参数查看一下官方网站。
http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html
Java1.8
http://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
Hotspotvm知识查看一下官方网站。
http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136373.html
主要的参数是:堆的大小、栈的大小、新生代和老年代的比值、新生代中eden和s0、s1的比值。
-Xms:初始堆大小,默认是物理内存的1/64。默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。例如:-Xms 20m。
-Xmx:最大堆大小。默认是物理内存的1/4 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
-XX:NewSize=xxx:设置年轻代大小(初始值)。
-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:每个线程的堆栈大小。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!