30公分浅淡JVM之基础

什么是JAVA虚拟机

JVM是JAVA虚拟机(JAVA Virtual Machine)的缩写,是一个虚构出来的计算机 ,是通过在实际 的计算机上仿真模拟各种计算机功能来实现的。JAVA虚拟机有自己完善的虚拟硬 件架构,如处理 器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台 相关的信息,使得 JAVA程序只需生成在JAVA虚拟机上运行的目标代码(字节码),就可以在多种平台 上不加修改地运行。

JVM运行流程

JVM、JRE 、JDK的区别和主流JVM

JVM JAVA虚拟机 所有的JAVA程序都是运行在JVM上 JVM是JRE的一部分

JRE JAVA Runtime Environment(JAVA运行环境) JRE是JDK的一部份 JRE主要用于执行JAVA程序,JRE除了包含JVM外还包含一些基础的JAVA API

JDK JAVA Development Kit(JAVA开发工具包) JDK提供了JAVA的开发环境和运行环境(JRE),开发环境主要包含了一些开发 工具,例如常用的JAVAC编译工具、jar打包执行 程序、还有一些JVM监控工具等

JIT 即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时, 会将 Java 字节码转换为本地代码,如,主要的热点 代码会被准换为本地代码,有利大幅度提高 Java 应用的性

Java技术体系

参考:https://docs.oracle.com/javase/7/docs/

主流JVM种类

除以上三个比较著名jvm外还有很多其它的JVM,例如: Azul VM、Liquid VM、Squawk

Bea和Sun两家公司已分别于2008、2010被Oracle收购;

JVM体系结构

Runtime data area 主要包括五个部分:

• Heap (堆)

• Method Area(方法区域)

• VM Stack(虚拟机栈),

• Native method stack(本地方法栈)(在Sun的HotSpot虚拟机中VM Stack和Native method stack是合并到一起的)

• Program Counter(程序计数器)

• Heap和Method Area是被所有线程的共享使用的;而VM Stack, Program counter 和Native method stack是 以线程为粒度的,每个线程独自拥有。

程序计数器:由于Java虚拟机的多线程是通过轮流切换并分配CPU执行时间的方式实现的,在任何一个确定的时刻,一个处理器内核都(多核处理器是一个内核)只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都必须有独立的程序计数器。

虚拟机栈:与程序计数器一样,虚拟机栈的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。(大家经常提到的Java的内存模型分为堆内存和栈内存,这里的栈就是虚拟机栈)。局部变量表存放了编译器可知的基本类型、对象引用和指向一条字节码指令的地址(returnAddress)。其中64位长度的longdouble类型会占用2个局部变量的空间,其余的类型只占用1个。局部变量所需的内存空间是在编译期间完成分配的,当进入这个方法时,需要在栈帧中分配多大的局部变量空间是确定的,在方法运行期并不会更改局部变量表的大小。虚拟机规范中针对虚拟机栈规定了2种异常情况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2、如果虚拟机栈可动态扩展(大部分虚拟机都可以动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请足够的内存,就会抛出OutOfMemoryError异常。

归纳一下就是:递归层次过深会出现栈溢出,申请内存超过上限就会出现内存溢出,经常在面试中会有面试官问:如何模拟出栈溢出和内存溢出,其实很简单,前者只需要无限递归,后者就是不断创建对象即可。

本地方法栈:本地方法栈与虚拟机栈的作用非常相似,它们之间的区别是虚拟机栈服务于虚拟机执行Java方法,而本地方法栈则服务于虚拟机用到的Native方法。虚拟机规范并没有对本地方法栈中方法使用的语言、使用方式和数据结构进行强制约定,因此虚拟机实现方可自由实现。比如Sun HotSpot虚拟机直接将本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,也会抛出StackOverflowErrorOutOfMemoryError异常。

方法区:线程共享内存区域,用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它还有一个被大家所熟知的别名:永久代,在JDK1.7中把原本存放在永久代中的字符串常量池移出去了。jdk1.6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面

Java堆:堆是Java虚拟机所管理的内存中最大的一块,堆是所有线程共享的,虚拟机启动时就创建了堆。几乎所有的对象实例都存放在堆内存中,虚拟机规范中这样描述:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术奖会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。堆是垃圾回收器管理的主要区域,因此很多时候被称为“GC堆”。

Sun HotSpot垃圾回收机制

• Eden区:

                • 启动JAVA程序时,在Eden中创建程序运行时必须的JAVA对象,当Eden的空间不足以用来创建新JAVA对象的时候,JVM的垃圾回收器执行对Eden区的垃圾回收 工作,销毁那些不再被其他对象引用的JAVA对象,并将那些被其他对象所引用的JAVA对象移动到幸存者0区。

• survivor0区和survivor1区

               • 如果surivor0区有足够空间存放则直接放到surivor0区;如果surivor0区没有足够空间存放,则JVM的垃圾回收器执行对surivor0区的垃圾回收工作,将那些被其 他对象所引用的JAVA对象移动到surivor1区。如果surivor1区有足够空间存放则直接放到surivor1区;如果surivor1区没有足够空间存放,则JVM的垃圾回收器执 行对surivor1区的垃圾回收工作,销毁那些不再被其他对象引用的JAVA对象,并将那些被其他对象所引用的JAVA对象移动到年老区。

• 老年代

             • 如果年老区有足够空间存放则直接放到年老区;如果年老区没有足够空间存放,则JVM的垃圾回收器执行对年老区的垃圾回收工作

• 持久代(方法区) ----------> (元空间)

              • JVM的规范中没有规定必须实现永久代的垃圾收集。也就是说,不一定必须实现。

JVM类加载器

默认类加载器 类加载器负责加载JAVA 类的字节代码到JAVA 虚拟机中,可以根据指定的类名(如 java.lang.Object) 来装载class文件的内容到Runtime data area中的method area(方法区域)。JAVA程序员 可以extends java.lang.ClassLoader类来写自己的Class loader。

一个类加载器接收到了类加载的请求,它首先把 这个请求委托给他的父类加载器去完成,每个层 次的类加载器都是如此,因此所有的加载请求都 应该传送到顶层的启动类加载器中,只有当父加 载器反馈自己无法完成这个加载请求(它在搜索 范围中没有找到所需的类)时,子加载器才会尝 试自己去加载。 java类随着它的类加载器一起具备了一种带有优 先级的层次关系。

从JVM的角度讲,主要有两种类型加载器:启动类加载器所有其它的类加载器。启动类加载器是JVM实现的一部分,使用C++语言 实现,其它类加载器都由java语言实现,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader

• Bootstrap ClassLoader 启动类加载器

         • 这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME$中 jre\lib\rt.jar(Sun JDK的实现)中所有class文件的加载,

        • 这个jar中包含了java规范定义的所有接口以及实现。启动类加载器无法被JAVA程序直接引用。

• Extension ClassLoader 扩展类加载器

        • 扩展类加载器负责加载\lib\ext目录中或者java.ext.dirs系统变量所指定的所有类库,开发者可以直接使用扩展类 加载器。 • Application ClassLoader 应用程序类加载器

        • JVM用此classloader来加载用户类路径(Classpath)上所指定的类库,包含指定的jar包以及目录,该加载器有时也称为系统类加 载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的 类加载器

        • Application ClassLoader只能加载项目bin目录下的.class文件。

• User-Defined ClassLoader 用户自定义类加载器

       • User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加 载非Classpath中的jar以及目录

类加载器的委托机制

(1). 首先当前线程的类加载器去加载线程中的第一个类(当前线程的 类加载器:Thread类中有一个 get/setContextClassLoader(ClassLoader cl);方法,可以获叏/指 定本线程中的类加载器)

(2). 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来 加载类B

(3). 还可以直接调用ClassLoader.loadClass(String className)方 法来指定某个类加载器去加载某个类,每个类加载器加载类时,又先 委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到収 起者类加载器,还加载丌了,则会抛出ClassNotFoundException

MyClassLoader---> AppClassLoader--->ExtClassLoader--->BootStrap

类加载器加载过程

加载
• 加载过程负责找到二迚制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载。
• 连接
• 链接过程负责对二迚制字节码的格式迚行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
• 验证:确保被导入类的正确性
• 准备:为类变量分配内存,并将其初始化为默认值
• 解析:把类中的符号引用转换为直接引用
• 初始化
• 初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触収执行:调
用了new;反射调用了类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类

内存调整和监控

JVM配置参数

JVM运行时数据区的内存大小可以通过参数来设置,通常能设置的两块区域为堆空间和持久代(方法区),设置方法是以参
数的形式来指定,Sun 的HotSpot需要在jvm启动前设置这些参数,启动JVM后丌能动态改变其大小。

 -Xms300m:堆空间初始大小
 -Xmx1024m:堆空间最大值(生产环境一般该值与Xms值设为一致,这样可以避免每次垃圾回收完成后对JVM堆大小迚行重新调
整)
 -Xmn512M:堆空间年轻代大小
 -XX:NewRatio=4:JVM堆的年轻代和老年代的大小比例为1:4
 -Xss128K:每个线程的堆栈大小为128K
 -XX:SurvivorRatio=6:新生代Surivor区(新生代有2个Surivor区)和Eden区的比例为2:6
 -XX:PermSize=150M  :持久代初始大小
 -XX:MaxPermSize=150M:持久代最大值
 -XX:MaxTenuringThreshold=1:新生代的对象经过几次垃圾回收后(如果还存活),迚入老年代。如果该参数设置为0,这表示
新生代的对象在垃圾回收后,不迚入survivor区,直接迚入老年代
 -XX:+HeapDumpOnOutOfMemoryError:该参数表示当JVM发生内存溢出时,自动在程序目录下生成DUMP文件,通过该文
件可以分析出什么原因导致内存溢出的
JVM堆空间的最大能分配多少不操作系统及硬件配置有关,32位的操作系统一般为2g以下,64位的操作系统基本可以说是没限制。

命令行工具

jps(JVM Process Status)虚拟机进程状况工具
命令行工具
jps命令格式:
jps [ options ] [ hostid ]
jps工具主要选项


 jstat(JVM Statistics Monitoring Tool)虚拟机统计信息监控工具
stat命令格式
jstat [ option vmid [ interval [ s | ms ] [ count 
] ] ] 

这个VMID,对于本地虚拟机迚程而言,VMID和LVMID是一致的。参数interval和count分别表示查询间
隔和次数,如果省略这两个参数,说明只查询一次,假设需要每250毫秒查询一次迚程2764的垃圾收集情
况,一共查询20次,那命令应当是:
jstat -gc 2764 250 20
选       项  作              用
-class  监视类装载、卸载数量、总空间以及类装载所耗费的时间
-gc
监视Java堆状况,包括Eden区、两个Survivor区、、老年代、永久带等的容量、已用空间、GC时间
合计等信息
-gccapacity  监视内容基本与-gc相同,但输出主要关注Java堆各个区域使用到的最大、最小空间
-gcutil  监视内容基本与-gc相同,但输出主要关注已使用的空间占总空间的百分比
-gccause  与-gcutil功能一样,但是会额外输出导致上一次GC产生的原因
-gcnew  监视新生代GC状况
-gcnewcapacity  监视内容基本与-gcnew相同,但输出主要关注使用到的最大、最小空间
-gcold  监视老年代GC状况
-gcoldcapacity  监视内容基本与-gcold相同,但输出主要关注使用到的最大、最小空间
-gcpermcapacity  输出永久代使用到的最大、最小空间
-compiler  输出JIT编译器编译过的方法、耗时等信息
-printcompilation  输出已经被JIT编译的方法
jstat主要工具选项 

查询结果表明,新生代Eden区(E,表示Eden)使用了2%的空间,两个Survivor区(S0、S1,表示Survivor0、Survivor1)
都是空的,老年代(O,表示Old)和永久带(P。表示Permanent)则分别使用了0%和13.84%的空间。程序运行以来共发
生Minor GC(YGC,表示Young GC)0次,总共耗时0秒;发生Full GC(FGC,表示Full GC)3次,Full GC共耗时(FGCT,
Full GC Time)为0秒,所有GC总耗时(GCT,表示GC Time)0秒。 

 jinfo(Configuration Info for Java)Java配置信息工具
jinfo命令格式
jinfo [ option ] pid
 jmap(Memory Map for Java)Java内存映像工具
命令用于生成堆转储快照。如果不使用jmap命令,要想获取Java堆转储,可以使用“-XX:+HeapDumpOnOutOfMemoryError”参
数,可以让虚拟机在OOM异常出现之后自动生成dump文件,Linux命令下可以通过kill -3发送进程退出信号也能拿到dump文件。
jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永久代的详细信息,如空间使用率、当前
使用的是哪种收集器等。 
除了生成dump文件的-dump选项和用于查看每个类的实例、空间占用统计的-histo选项在所有操作系统都提供之外,其余选项都
只能在Linux和Solaris系统下使用。

jmap命令格式
jmap [ option ] vmid
jmap工具主要选项

 jstack(Stack Trace for Java)命令用于生成虚拟机当前
时刻的线程快照
线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的主要是定位线程长时间出现停顿的原
因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过
jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
jstack命令格式
jstack [ option ] vmid
jstack主要工具选项

JConsole

• 配置jvm监控参数
• 该参数需在jvm启动前配置,其配置方法及位置同设置jvm堆大小方法一样具体
参数及说明如下:
• -DJAVA.rmi.server.hostname=127.0.0.1:指定当前主机的IP
• -Dcom.sun.management.jmxremote.port=9880:指定使用那个端口作为监控的
端口
• -Dcom.sun.management.jmxremote.ssl=false :
• -Dcom.sun.management.jmxremote.authenticate=false:不需要使用口令
• 运行JVM监控工具Jconsole
• 在命令行窗口输入“jconsole”并回车即可(前提是配置了JAVA环境变量,如果
未配置可以进入到jdk的bin目录下手动执行“jconsole.exe”程序)
• 选择需要监控的jvm 
• 监控本地JVM,直接选中本地的JAVA进程,点连接即可
• 监控远程JVM,先选择远程监控,并在文本框中填写远程jvm的ip地址及监控端
口号如果远程jvm开启口令认证的话还需要输入用户名和口令

Eclipse Memory Analyzer(MAT)

• 通过jvm参数--XX:-HeapDumpOnOutOfMemoryError可以让JVM在出现内存溢出是Dump出当前的内存转储快照;
• 用jmap生产dump文件,用jmap命令(Java5:jmap -heap:format=b <pid>;
Java6:jmap -dump:format=b,file=HeapDump.bin <pid>);
• WAS/Weblogic等应用服务命令生成

JProfile

IBM JVM相关工具

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值