天天看点

jvm常用命令与体系结构-1

1、jvm学习技巧

我们学习java时解决问题时不应该一遇到问题就百度、谷歌。而是应该区官网找答案,找第一手资料。
           

java官方文档:https://docs.oracle.com/javase/8/docs/

jvm常用命令与体系结构-1

从官网这张图就可以看出JDK、JRE、JVM之间的关系。如果想看对应的组件技术,点击图标就可进入查看。

2、jvm信息查看常用命令

命令学习也应查看官方文档:

jvm常用命令与体系结构-1

点击“search”进入;

jvm常用命令与体系结构-1

收索相应的命令即可。

(1)jps

JPS 名称: jps - Java Virtual Machine Process Status Tool

命令用法: jps [options] [hostid]

options:命令选项,用来对输出格式进行控制

          hostid:指定特定主机,可以是ip地址和域名, 也可以指定具体协议,端口。

          [protocol:][[//]hostname][:port][/servername]
           

功能描述: jps是用于查看有权访问的hotspot虚拟机的进程. 当未指定hostid时,默认查看本机jvm进程,否者查看指定的hostid机器上的jvm进程,此时hostid所指机器必须开启jstatd服务。 jps可以列出jvm进程lvmid,主类类名,main函数参数, jvm参数,jar名称等信息。

命令选项及功能:

没添加option的时候,默认列出VM标示符号和简单的class或jar名称.如下:

jvm常用命令与体系结构-1

-l: 输出应用程序主类完整package名称或jar完整名称.

jvm常用命令与体系结构-1

-v: 列出jvm参数

jvm常用命令与体系结构-1

(2)jinfo

命令用法: jinfo [options] [hostid]

jinfo列出当前java进程相关jvm内存大小分配情况等。命令: jinfo -flags java子进程id

jvm常用命令与体系结构-1

(3)jmap

命令用法: jmap [options] [hostid]

我们常用jmap来打印虚拟机堆内存使用情况等:> jmap -heap java子进程id

jvm常用命令与体系结构-1

(4)编译器参数

-Xms20m Java堆初始容量20M

-Xmx20m Java堆最大容量20M

-Xmn10m Java堆年轻代大小10M

-XX:+PrintGCDetails 打印GC信息

-XX:+PrintGCDateStamps 打印GC时间

-XX:SurvivorRatio=8 n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如8,表示Eden:Survivor=8:2,一个Survivor区占整个年轻代的1/10

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=D:/logs

在IDEA或Eclipse编译器设置JVM参数即可打印相关JVM参数:

jvm常用命令与体系结构-1

打印相关堆信息,以及空间使用信息等。

jvm常用命令与体系结构-1

(5)Tomcat配置GC日志

tomcat目录/bin

找到catalina.sh(这是linux下,window下应该是catalina.bat)

配置JAVA_OPTS参数。我这里配置的是tomcat目录/log目录下

JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS -Xms1024m -Xmx3048m -XX:PermSize=512m -XX:MaxPermSize=1524m -Xss4096K -Xloggc:…/logs/tomcat_gc.log "

3、jvm体系结构

一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

jvm常用命令与体系结构-1

Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

参考《Java虚拟机规范(第7版)》的描述,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图所示:

jvm常用命令与体系结构-1

可以看出Java虚拟机的运行时数据区包括了:方法区、Java堆、Java虚拟机栈、PC寄存器、本地方法栈,还有常量池。它们被分为两大类-------线程共享、私有数据区。

1.线程共享数据区

包括:Java堆、方法区、常量池。它们会随着虚拟机启动而创建,随着虚拟机退出而销毁。

(1)Java堆

推荐文章:http://blog.csdn.net/ljheee/article/details/52196455

Java堆在虚拟机启动的时候被创建,Java堆主要用来为类实例对象和数组分配内存。Java虚拟机规范并没有规定对象在堆中的形式。对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT(Just In Time)编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了-----可是使用逃逸分析和栈帧存储技术。

如果从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

参考《Java虚拟机规范(第7版)》的描述,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。
           

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old ) (jdk1.8以前包含永久代,jdk1.8开始改为了元空间);这也就是JVM采用的“分代收集算法”,简单说,就是针对不同特征的java对象采用不同的策略实施存放和回收,自然所用分配机制和回收算法就不一样。新生代 ( Young ) 又被划分为三个区域:Eden(伊甸区)、From Survivor(S0)、To Survivor(S1)。(S0和S1是相互拷贝的,比如S0进行GC时会将可达的对象拷贝到S1中,然后全部清理S0;下次又从S1到S0这样相互拷贝)

分代收集算法:采用不同算法处理[存放和回收]Java瞬时对象和长久对象。大部分Java对象都是瞬时对象,朝生夕灭,存活很短暂,通常存放在Young新生代,采用复制算法对新生代进行垃圾回收。老年代对象的生命周期一般都比较长,极端情况下会和JVM生命周期保持一致;通常采用标记-压缩算法对老年代进行垃圾回收。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
           

Java堆可能发生如下异常情况:如果实际所需的堆超过了自动内存管理系统能提供的最大容量,那Java虚拟机将会抛出一个OutOfMemoryError异常。

(2)方法区

方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。 

    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

    对于习惯在HotSpot虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9等)来说是不存在永久代的概念的。即使是HotSpot虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”至Native Memory来实现方法区的规划了。
           

Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun公司的BUG列表中,曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

方法区可能发生如下异常情况: 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常.

(3)常量池

Jdk1.6及之前:常量池1.6在方法区

Jdk1.7:但已经逐步“去永久代”,常量池1.7在堆

Jdk1.8及之后:常量池1.8在元空间

运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池在方法区中。

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。 既然运行时常量池是方法区的一部分自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

在创建类和接口的运行时常量池时,可能会发生如下异常情况:当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那Java虚拟机将会抛出一个OutOfMemoryError异常。

2.线程私有数据区

包括:PC寄存器、JVM栈、本地方法区。它们是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

(1)PC寄存器

PC(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

    每个Java虚拟机线程都有自己的PC寄存器。在某个线程被新建时,会获得一个PC寄存器。线程当前执行的方法称为当前方法,PC寄存器用来存放当前方法中当前执行的字节码指令的地址;之所以为每一个线程都分配一个PC寄存器,试想:多线程运行时,某个时间片内只执行一个线程,CPU在不停的切换多个线程,那如何记录具体每一个线程上一次执行到哪个位置了呢,这时候PC寄存器用来存放当前方法中当前执行的字节码指令的地址,就完美解决了,这就是为什么PC寄存器是线程私有数据区的原因。

    如果当前方法是本地方法(Native),那么寄存器存放undefined。寄存器的大小至少应该能够存放一个returnAddress类型的数据或者与平台相关的本地指针的值。
           

PC寄存器是惟一一个没有明确规定需要抛出OutOfMemoryError异常的运行时数据区。

(2)JVM栈

每个Java虚拟机线程都有自己的Java虚拟机栈。Java虚拟机栈用来存放栈帧,而栈帧主要包括了:局部变量表、操作数栈、动态链接。Java虚拟机栈允许被实现为固定大小或者可动态扩展的内存大小。

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

经常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“堆”在后面会专门讲述,而所指的“栈”就是现在讲的虚拟机栈,或者说是虚拟机栈中的局部变量表部分。

Java虚拟机使用局部变量表来完成方法调用时的参数传递。局部变量表的长度在编译期已经决定了并存储于类和接口的二进制表示中,一个局部变量可以保存一个类型为boolean、byte、char、short、float、reference 和 returnAddress的数据,两个局部变量可以保存一个类型为long和double的数据。
           

Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果。

每个栈帧中都包含一个指向运行时常量区的引用支持当前方法的动态链接。在Class文件中,方法调用和访问成员变量都是通过符号引用来表示的,动态链接的作用就是将符号引用转化为实际方法的直接引用或者访问变量的运行是内存位置的正确偏移量。

总的来说,Java虚拟机栈是用来存放局部变量和过程结果的地方。 
           

Java虚拟机栈可能发生如下异常情况: 如果Java虚拟机栈被实现为固定大小内存,线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,Java虚拟机将会抛出一个StackOverflowError异常。 如果Java虚拟机栈被实现为动态扩展内存大小,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。

(3)本地方法区

本地方法栈用于支持native方法的运行。(native方法,比如用C/C++实现的代码)。
           

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。