JVM基础知识汇总 ( 看这一篇就够了,面试使用全无压力 )

1.jvm是什么

       Java Virtual Machine(Java虚拟机),它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的.

       本文jvm是基于jdk7写的,关于jdk8的jvm,和jdk7的稍有不同,详见文章末尾链接


2.jvm能做什么

       java语言之所以可以跨平台,就是jvm屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行.Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行.这就是Java的能够“一次编译,到处运行”的原因.


3.jvm分类

3.1 SUN公司(已被oracal收购)有好几款虚拟机,包括hotSport
3.2 BEA公司:jRockit(专注于服务器端应用)
3.3 IBM公司:J9 VM(用于作为IBM公司各种java产品的执行平台)
3.4 微软:Microsoft JVM(为了在浏览器中运行java程序,已经被sun公司搞死了)
3.5 其他(还有很多)

最常熟知的就是sun公司的hotSpotVm,也就是我们常用的vm.
我也是主要以这个为重点来展开的


4.jvm内存区域(HotSpot)

这里写图片描述

4.1 程序计数器(线程私有)

       占用很小的内存空间,可以看成是当前线程所执行的字节码的行号指示器,所以他是每个线程私有的,各线程的计数器互不影响,独立存储

4.2 java栈(线程私有)

4.2.1 java虚拟机栈

       执行java方法

4.2.2 本地方法栈

       执行本地方法(可以理解为java同c语言的接口)

       每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口的信息.

局部变量表:
------存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)
------对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用.
------局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的.

操作数栈:
------所有计算过程都是在借助于操作数栈来完成的.

方法出口:
------方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

4.3 java堆(线程共享)

       虚拟机启动的时候创建,主要用来存放对象实例,GC的主要阵地.

4.3.1 新生代

------Eden
------Form Survivor
------To Survivor

4.3.2 老年代

4.4 方法区(线程共享)

非堆永久代(hotSpot特有),为了让垃圾收集器像管理堆一样管理这部分内存.
方法区存储了每个类的信息,比如:

4.4.1.Classloader 引用
4.4.2.字段数据(每个字段的字段名,类型,修饰符,属性)
4.4.3.方法数据(每个方法的方法名,返回值类型,参数类型,修饰符,属性)
4.4.4.方法代码(每个方法的字节码,操作数栈大小,局部变量大小,局部变量表,异常表,每个异常处理器,开始点,结束点,异常处理代码的程序计数器(PC)偏移量,被捕获的异常类对应的常量池下标)

       因为所有线程共享同一个方法区,因此访问方法区数据的和动态链接的进程必须线程安全。如果两个线程试图访问一个还未加载的类的字段或方法,必须只加载一次,而且两个线程必须等它加载完毕才能继续执行。

4.5 运行时常量池

jvm7之前属于方法区,用来存储数值型常量,字段引用,方法引用,属性
关于常量池,字符串常量池,运行时常量池区别,请戳这里:
区别

java内存对象模型如下图
这里写图片描述

5.jvm垃圾收集算法

5.1 标记-清除算法

先标记需要回收的对象,后统一清除,缺点是效率不高,而且会产生大量不连续的内存碎片,导致后面的大对象无法存储,从而导致再一次收集.

5.2 复制算法(适用于对象存活率较低的内存区域,比如新生代)

先将可用内存划分成两块A B,每次使用一块A,当这块A满了,就将这块中还存活的对象放在另一块B上,然后一次性清理掉A.复制算法简单粗暴效率高.

如果将AB的内存比例设置为1:1,那么相当于将可用内存减半了,太不划算,考虑到98%的对象都是朝生夕死,所以将新生代分为三块:Eden,Form Survivor,To Survivor,默认8:1:1

(两个survivor,是为了保证任何时候都有一个survivor是空的)

5.3 标记-整理算法(适用于对象存活率高的内存区域,比如老年代)

先标记需要回收的对象,然后让所有存活的对象都向一端移动,最后清理掉端边界以外的内存,这样一次收集后,空闲的内存会比较连续,更容易存放大对象.
同样的,这个算法,效率更低.

6.JVM垃圾收集器

首先说收集器的目标:低停顿,高吞吐,大覆盖
a. 停顿时间
停顿时间越短就越适合于用户交互的程序,良好的响应速度能提升用户体验。
b. 吞吐量
高吞吐量则可以高效率利用CPU时间,尽快完成运算任务。适合在后台计算而不需要太多交互的任务。
c. 覆盖区
在达到前面两点的情况下,尽量减少堆的内存空间,可以获得更好的空间局部性。

6.1 Serial收集器 ['sɪərɪəl]

这里写图片描述
单线程收集器.
特点1:简单高效
特点2:Stop The Word.即收集垃圾的时候,会把其他线程全部停掉
“-XX:+UseSerialGC” : 该参数用来显示的添加串行垃圾收集器

6.2 Parnew收集器['pɑːnjuː]

这里写图片描述
是Serial收集器的多线程版本
特点:除了Serial收集器,目前只有Parnew收集器能与CMS收集器配合工作
“-XX:+UseConcMarkSweepGC”:指定使用CMS后,会默认使用ParNew作为新生代收集器
“-XX:+UseParNewGC”:强制指定使用ParNew收集器
“-XX:ParallelGCThreads”:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同

6.3 Parallel Scavenge收集器 ['pærəlel]['skævɪndʒ]

特点1:关注点在于吞吐量,而其他线程一般都注重缩短垃圾回收时,用户停顿时间
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
特点2:多了一个GC自适应调节策略,-XX:+UseAdaptiveSizePolicy
“-XX:MaxGCPauseMillis”:控制最大垃圾收集停顿时间
“-XX:GCTimeRatio”:直接设置吞吐量大小(0-100),默认值是99,就是允许最大1%(即 1/(1+99))的垃圾收集时间

6.4 Serial Old收集器

Serial收集器的老年代版本,
特点:单线程收集器.新生代采取复制算法,老年代采取标记整理算法

6.5 Parallel Old收集器

这里写图片描述
Parallel Scavenge收集器的老年代版本,1.6开始出现
特点:多线程,标记整理算法,主要是和Parallel Scavenge配合使用
“-XX:+UseParallelOldGC”:指定使用Parallel Old收集器。

6.6 CMS收集器(Concurrent Mark Sweep)

这里写图片描述
标记清除算法
特点:回收停顿时间短,并发收集
“-XX:+UseConcMarkSweepGC”: 指定使用CMS收集器(用了这个后,年轻代会默认使用ParNew收集器)
“-XX:CMSInitiatingOccupancyFraction=75” :是指设定CMS在对内存占用率达到75%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC)
“-XX:+UseCMSInitiatingOccupancyOnly”:只是用设定的回收阈值(上面指定的75%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
一般情况下,"-XX:CMSInitiatingOccupancyFraction=75"和"-XX:+UseCMSInitiatingOccupancyOnly"是配合使用的

CMS缺点1:并发导致cpu资源敏感,尤其是4C以下,CMS默认的收集线程数量是 = (CPU数量 + 3)/4,所以说核数越大,CMS并发对用户的影响越小
解决 : 多核cup考虑一下
CMS缺点2:浮动垃圾(并发清除的时候产生的垃圾)可能导致再一次FullGC(启动Serial Old收集器),这次收集时间较长
解决 : “-XX:CMSInitiatingOccupancyFraction”:设置CMS预留内存空间,就是给浮动垃圾用的,当这块内存不够用的时候,就会再次导致FullGC
CMS缺点3:产生大量内存碎片(标记清除算法 )
解决1: “-XX:+UseCMSCompactAtFullCollection”,默认开启,用于fullGC前的合并整理,但这个整理过程需要停顿
解决2: “-XX:+CMSFullGCBeforeCompaction”,默认0,当执行n次不压缩的fullGC后,下次FullGC带压缩

总而言之,服务器内存越大,核数越多,就越适合用CMS

6.7 G1收集器

这里写图片描述

堆空间分割收集

特点1:使用并发让GC的时候,应用程序继续执行(G1在另一个CPU上运行)
特点2:分代收集,但不需要其他收集器配合,能够采用不同方式处理不同时期的对象,但新生代和年老代不再是物理隔离,它们都是一部分Region(不需要连续)的集合
特点3:整体标记整理,局部复制算法(堆划分为多个大小相等的独立区域Region),总之不是标记清除算法
特点4:做到实时垃圾收集(RTSJ),即可预测的停顿,建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒,这样在收集的时候就可以有选择的在用户设置的时间范围内回收最优先需要回收的region(G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表)
特点5:整体优于CMS的低停顿

“-XX:UseG1GC”:指定使用G1收集器.
“-XX:MaxGCPauseMillis”:为G1收集器设置暂停时间目标,默认值为200毫秒
“-XX:G1HeapRegionSize”:设置每个Region大小,范围1MB到32MB;在最小Java堆时可以拥有大约2048个Region

这里写图片描述

注:
在G1中,还有一种特殊的区域,叫Humongous区域。 
如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。
这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。
如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。
为了能找到连续的H区,有时候不得不启动Full GC

垃圾收集器关系图:

这里写图片描述

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

7.jvm可视化

8.jvm调优

-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间
-Xloggc:../logs/gc.log 日志文件的输出路径
-XX:+HeapDumpOnOutOfMemoryError //发生OOM的时候自动dump堆栈方便分析

9.jvm常见问题

       9.1 如果A和B对象循环引用,是否可以被GC?
       9.2 如何判断对象是否需要回收,有哪几种方式?
       9.3 Java中能不能主动触发GC?
       9.4 Java中的内存溢出是什么,和内存泄露有什么关系?
       9.5 Java中的内存溢出是什么,和内存泄露有什么关系?
       9.6 ClassLoader的类加载方式

10.jvm–jdk1.8

参考:
https://blog.csdn.net/joeyon1985/article/details/39080125

11.jvm本地小测试

参考:
https://blog.csdn.net/universe_ant/article/details/58585854

猜你喜欢

转载自blog.csdn.net/java_zhangshuai/article/details/79967242