JVM知识回顾
1. 简述JDK,JRE和JVM之间的关系
-
JDK:
是用于支持Java程序开发的最小环境,基本上Java程序设计语言、Java虚拟机、Java API类库这三部分
JDK=JRE+java开发工具包
作用:java程序的开发环境
-
JRE:
是支持Java程序运行的标准环境,Java API类库中的Java SE API自己和Java虚拟机这两部分
java运行环境=JVM+核心类库
作用:java程序的运行环境
-
JVM:
- 保证 Java程序设计语言的安全特性和java语言的跨平台性
- 一次编译,到处运行(Write once,run anywhere)
简言之,使用 jdk 开发的java程序,交由 JRE 来运行,由 JVM 来保证跨平台
2. 类加载机制的作用和过程
一句话解释:类的加载指的是将.class文件中的二进制数读入到内存中,将其放在运行数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
类的加载过程包括加载、验证、准备、解析、初始化五个阶段
1)加载
在加载阶段,虚拟机主要做了三件事
- 通过一个类的全限定类名来获取其定义的二进制字节流
- 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
- 在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口
2)验证
保证被加载类的准确性
- 文件格式的验证:验证.class文件字节流是否符合class文件的格式规范,并且能够被当前版本的虚拟机处理。这里主要对魔数、主版本号、常量池等等的校验
- 元数据验证:主要对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求。比如说验证这个类是不是有父类,类中的字段方法是不是和父类有冲突
- 字节码验证:这是整个验证过程中最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出危害虚拟机的事。
- 符号引用验证:他是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。
3)准备
准备阶段主要为类变量分配内存并设置初始值
类变量(static)会分配内存,但实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中
-
这里的初始值值得是数据类型默认值,不是代码中被显示赋予的值。比如
public static int a = 1;
这里的a在准备阶段过后值为0,而不是1。赋值为1的动作在初始化阶段。
4)解析
解析阶段主要是虚拟机将常量池中的符号引用转为直接引用的过程
- 符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量。classfile中的内容,没有实际含义
- 直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。在java进程中能够代表真实含义的
5)初始化
主要为类的静态变量赋予正确的初始值,有两种方式
- 声明类变量时指定初始值
- 使用静态代码块为类变量指定初始值
3. 运行时数据区分为哪几块?说说你对每块区域的理解(某些存储的内容,生命周期,作用等)
运行时数据区分为方法区、堆、虚拟机栈、本地方法栈、程序计数器
- 程序计数器:线程私有,可以看成是当前线程所执行的字节码的行号指示器,用于选取下一条需要执行的指令,也是唯一一个没有OOM情况的区域,与线程共存亡
- Java虚拟机栈:线程私有,为JVM执行Java方法服务,主要描述Java方法执行的线程内存模型,与线程共存亡
- 本地方法栈:线程私有,与虚拟机栈相似,为JVM使用到的Native方法服务,Native方法多用C++写
- Java堆:线程共享,主要用于存储实例对象,Java中几乎所有的对象实例都会存储到这块内存中,考虑到即时编译和逃逸分析,有一部分的对象实例会分配到栈上
- 方法区:线程共享,主要用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据
- 运行时常量池:线程共享,属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用
4. 结合Eden,S0,S1和Old区,描述一下一个对象创建的过程
对象的分配,从理论上来讲应该都是在堆上分配(即时编译和逃逸分析,会分配到栈上)。在正常分代的情况中,新生成的对象会分配在新生代中,但是少数情况下(比如对象大小超过一定阈值)会直接分配到老年代。
大多数情况下,对象在Eden区中分配,当Eden区没有足够空间时,虚拟机会发起一次Minor GC,新生代采用的是复制算法,把存活的对象从Eden和S0复制到S1区,后把对象年龄加一,并清空Eden和S0(新生代的对象大多为活跃的对象,会频繁的创建和销毁,所以新生代GC时应该存活的对象很少,所以采用复制算法是最合适的)。当对象达到一定年龄之后(默认15岁,可修改-XX:MaxTenuringThresold=需要的年龄数字),对象会被移动到老年代,当然老年代如果空间不足,会发生Major GC,采用标记整理算法(老年代的对象大部分都是经过一段Minor GC的,所以相对比较稳定,每次回收的对象相对比较少,所以采用标记整理算法最合适)。
另外,一个对象的创建过程,上述描述只是一个对象的创建过程中的一个步骤中的详解,一个对象的创建,从虚拟机的角度来看,包括:遇到new指令—判断引用能否在常量池定位;检查引用能否被加载、解析、初始化过—分配内存(包括指针碰撞和空闲列表法,这一部分内容是我上面的描述)—初始化内存空间—设置对象头—执行init()方法。
5. 怎样确定一个对象为垃圾
-
引用计数法
引用计数算法就是在对象中添加了一个引用计数器,当有地方引用这个对象时,引用计数器的值就加1,当引用失效的时候,引用计数器的值就减1。当引用计数器的值为0时,jvm就开始回收这个对象。
简单的来说,在JVM中的栈中,如果栈帧中指向了一个对象,那么堆中的引用计数器的值就会加1,当栈帧这个指向null时,对象的引用计数器就减1。
这种方法虽然很简单、高效,但是JVM一般不会选择这个方法,因为这个方法会出现一个问题:当对象之间相互指向时,两个对象的引用计数器的值都会加1,而由于两个对象时相互指向,所以引用不会失效,这样JVM就无法回收。
可达性分析法(Reachability Analysis)
所有生成的对象都是一个称为"GC Roots"的根的子树。从GC Roots开始向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到“GC Roots”没有任何引用链可以到达时,就称这个对象是不可达的(不可引用的),也就是可以被GC回收了。
6. 常用的垃圾回收算法有哪些?有什么优缺点
-
**标记-清除 **
分为两个阶段:标记和清除。根据根节点可达分析哪些对象为垃圾,并对垃圾进行标记,然后统一回收。这是最基础的算法,后续的收集算法都是基于这个算法扩展的。
不足:
扫描整个堆内存,耗时,效率低;
标记清除之后会产生大量碎片。
-
标记整理
分为两个阶段:标记和整理。根据根节点可达分析哪些对象为垃圾,并对垃圾进行标记,将还在被使用的对象移动到一端。然后在清理掉这个范围之外的垃圾。
优点:
- 避免了内存碎片
缺点:
- 整理内存耗时
-
复制
将内存按容量划分为大小相等的两块,每次只使用其中一块,当这一块内存用完,将还在被使用的对象复制到另一块上,再将已使用过的那一块内存空间全部清理掉。
优点:
- 效率高,实现简单
缺点:
- 内存空间利用率只用50%
7. 简述你对吞吐量和停顿时间的理解
在实践活动中,我们通过最优吞吐量和最短停顿时间来评价jvm系统的性能:
吞吐量越高算法越好
暂停时间越短算法越好
首先让我们来明确垃圾收集(GC)中的两个术语:吞吐量(throughput)和暂停时间(pause times)。 JVM在专门的线程(GC threads)中执行GC。只要GC线程是活动的,它们将与应用程序线程(application threads)争用当前可用CPU的时钟周期。简单点来说,吞吐量是指应用程序线程用时占程序总用时的比例。例如,吞吐量99/100意味着100秒的程序执行时间应用程序线程运行了99秒,而在这一时间段内GC线程只运行了1秒。
术语”暂停时间”是指一个时间段内应用程序线程让与GC线程执行而完全暂停。例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的。 如果说一个正在运行的应用程序有100毫秒的“平均暂停时间”,那么就是说该应用程序所有的暂停时间平均长度为100毫秒。同样,100毫秒的“最大暂停时间”是指该应用程序所有的暂停时间最大不超过100毫秒。
8. 常见的jdk命令和工具有什么?并简述一下他们的作用
常见的jdk命令:
-
jps
jps是JVM Process Status Tool的简称,用于显示指定系统内所有的HotSpot虚拟机进程。常用的命令有:
jps -l //用于输出主类的全名,如果运行的是jar包,则输出jar路径;
jps -v //用于输出虚拟机启动时的JVM参数。
[qianfg@Centos7 ~]$ jps -l 6563 eurekaserver-0.0.1-SNAPSHOT.jar 6564 manage-0.0.1-SNAPSHOT.war 6565 mybank-0.0.1-SNAPSHOT.war 6567 overseas-0.0.1-SNAPSHOT.war 5639 sun.tools.jps.Jps 17898 org.apache.rocketmq.namesrv.NamesrvStartup 17900 org.apache.rocketmq.broker.BrokerStartup 22783 org.apache.catalina.startup.Bootstrap
-
jstat
jstat是JVM Statistics Monitoring Tool的简称,用于显示本地或远程虚拟机进程中的类加载、内存、垃圾回收、JIT编译等运行时数据。
jstat -class pid //类装载信息
[qianfg@Centos7 ~]$ jstat -class 6563 Loaded Bytes Unloaded Bytes Time 11234 19528.8 0 0.0 60.17
-
jinfo
jinfo是Configuration Info for Java的简称,用于显示虚拟机各项参数。常用的命令有:
jinfo -flag pid //用于查看未被显示指定的参数的默认值;
jinfo -syspros pid //用于输出虚拟机进程的System.getProperties()的内容。
[qianfg@Centos7 ~]$ jinfo -flag InitialHeapSize 22783 -XX:InitialHeapSize=62914560
-
jmap
jmap是Memory Map for Java的简称,用于生成堆转储快照文件、查询finalize执行队列、Java堆和永久代的详细信息,需要配合具体的选项参数使用。
[qianfg@Centos7 ~]$ jmap -heap 22783 Attaching to process ID 22783, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.221-b11 using thread-local object allocation. Parallel GC with 2 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 994050048 (948.0MB) NewSize = 20971520 (20.0MB) MaxNewSize = 331350016 (316.0MB) OldSize = 41943040 (40.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: PS Young Generation Eden Space: capacity = 18350080 (17.5MB) used = 14596944 (13.920730590820312MB) free = 3753136 (3.5792694091796875MB) 79.54703194754464% used From Space: capacity = 1572864 (1.5MB) used = 131072 (0.125MB) free = 1441792 (1.375MB) 8.333333333333334% used To Space: capacity = 1572864 (1.5MB) used = 0 (0.0MB) free = 1572864 (1.5MB) 0.0% used PS Old Generation capacity = 29884416 (28.5MB) used = 19405328 (18.506362915039062MB) free = 10479088 (9.993637084960938MB) 64.93460671943531% used 16678 interned Strings occupying 1501192 bytes.
-
jstack
jstack是Stack Trace for Java的简称,用于显示当前虚拟机内每一条线程正在执行的方法堆栈集合,可用于分析线程长时间卡顿的原因。
[qianfg@Centos7 ~]$ jstack 22783 2020-03-12 02:48:06 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode): "Attach Listener" #51 daemon prio=9 os_prio=0 tid=0x00007ff7fc007000 nid=0x1bbe waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "http-nio-8080-exec-15" #50 daemon prio=5 os_prio=0 tid=0x00007ff808026000 nid=0x3c29 waiting on condition [0x00007ff7f01c1000] java.lang.Thread.State: WAITING (parking) ...此处多行省略 "VM Thread" os_prio=0 tid=0x00007ff824074000 nid=0x5903 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007ff82401f800 nid=0x5901 runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007ff824021000 nid=0x5902 runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007ff8240c5800 nid=0x590a waiting on condition JNI global references: 212