首页 Jvm
文章
取消

Jvm

JVM

⭐JVM总述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
Java文件(.java):通过javac命令生成字节码文件

一.class文件:字节码文件
	魔数:标识该文件是否是class文件(是一个固定的值:0XCAFEBABE) 
	主次版本号:
	常量池:
		 字面量:
		 符号引用:(对应L类加载的解析步骤:把常量池中的符号引用改为直接引用)
		 	 类和接口的全限定名
		 	 字段的名称和描述符
		 	 方法的名称和描述符
		 
		 class文件中的项constant_pool_count的值为1, 说明每个类都只有一个常量池
		 常量池中的数据也是一项一项的没有间隙的依次排放。
		 常量池中各个数据项通过索引来访问有点类似与数组只不过常量池中的第一项的索引为1, 而不为0.	
         
	类或接口的访问修饰符:
	类索引:
	父类索引:
	接口索引的集合:
	字段表集合:描述一个字段的信息
		作用域: public protected private
		是否为静态变量: static
		可变性: final
		序列化: transient
		并发可见性: volatile
		数据类型: int double
		字段名: student1..
	方法表集合:描述一个方法的信息
	属性表:描述一个额外的信息
	
二.类加载器:用来加载class类文件到运行时数据区
	类加载器分类:
		启动类加载器:加载 jre\lib文件下的类
		拓展类加载器:加载 jre\lib\ext文件下的类
		应用程序类加载器:加载 class path 文件下的类
		自定义类加载器:继承CLassLoader类
        	重写findclass方法:保持双亲委派模型
        	重写loadclass方法:不保持双亲委派模型
     双亲委派模型:
       双亲委派的概述:
     	在加载类到运行时数据区的时候 类加载器收到类加载请求 优先把这个请求 给父类加载器区处理 
     	父类加载器收到请求 如果自己没有父类 则处理请求 如果有则还是交给父类的父类去处理
     	如果处理成功 则后续有同样的类要加载 会使用父类加载的类 而不是子类
        如果处理失败则交给向下交给处理类加载请求
        
      双亲委派的打破:
      	 ①自定义类加载器:继承CLassLoader类 重写loadclass方法:不保持双亲委派模型
         ②自身缺陷:双亲委派模型解决了越是基础的类 就越优先加载 但是如果基础类要调回自己的用户代码 则要打破				 双亲委派模型 使用线程上下文类加载器	
       
     
     类加载的过程:
     	·加载: 
     		 通过class文件中 常量池的符号引用中的 类和接口的全限定名
     		  把class字节码文件转化成该类的二进制流数据 
              解析这个二进制流把它的静态数据结构转化方法区中的动态的数据结构
              创建一个该类型的运行时类的实例 (java.lang.classL类实例) 
              此过程有父类先加载父类 
              懒加载
              
     	·链接:
     		验证: 验证加载后的二进制数据流信息是否对JVM有害
     		准备: 在方法区为静态变量分配空间 以及 赋默认值
     		解析: 把常量池中的符号引用改为直接引用-->静态链接(类加载时)[对比虚拟机栈的动态链接(方法运行时)]
     		
     	·初始化:执行clint()方法 执行赋值操作 有父类先执行父类的赋值 而且是懒加载 需要的时候才加载
     		   把非final静态变量赋初始值 执行静态代码块
     		  
        	  static final 类型变量存放在哪?
        	  	首先它不进行初始化 它是一个常量
       			调用这个static final 变量的时候 会把它加载自身类(main方法中)或者方法区的常量池的字面量中
       			(大于short.MAX_VALUE)
         
三.运行时数据区:
	·栈:处理方法的执行和调用
		虚拟机栈:
			处理普通方法的执行服务
			每一次调用普通方法时 就会创建一个栈帧它包含
				局部变量表:保存局部的遍历 包括基本数据类型 对象的引用
				操作数栈:
				动态连接:将方法区中运行时常量池的符号引用改为直接引用
				方法的出入口信息:
			一个方法的开始和结束就对应一个栈帧的入栈和出栈
		本地方法栈:
			为本地方法提供服务
	·堆:
		存放实例对象和数组对象的区域 GC主要回收的区域
		
		分区:
			新生代:
				Eden:首次对象被分配到这
				FromSurvivor:
				ToSurvivor:
			老年代:用于存放大对象
		GC:
		   垃圾回收器 通过一定的 垃圾回收算法 回收 垃圾
        	垃圾:
        		通过可达性分析法 判断对象是否是垃圾
        		通过GCRoot的对象作为起点从这些起点开始向下遍历
        		如果GcRoot后面没有相连接的引用则说明该对象是垃圾
        		我们把遍历对象过程中遇到的对象,按"是否访问过"这个条件标记成以下三种颜色:
        		 白色:尚未访问过
				黑色:本对象已访问过,而且本对象引用到的其他对象也全部访问过
				灰色:本对象已访问过,但是本对象引用到的其他对象尚未全部访问完
				具体过程:
				1.初始时,所有对象都在【白色集合】中;
				2.将 GC Roots 直接引用到的对象放到【灰色集合】中;
				3.从灰色集合中获取对象:
					3.1. 将本对象引用到的其他对象全部挪到 【灰色集合】中;
					3.2. 将本对象挪到【黑色集合】里面。
				4.重复步骤3,直至【灰色集合】为空时结束。
				5.结束后,仍在【白色集合】的对象即为 GC Roots 不可达,可以进行GC
				
				
        		可作为GC Root对象的引用:
        			1.虚拟机栈的引用对象
        			2.方法区中类静态变量引用对象
        			3.方法区中常量池引用对象
        			4.本地方法引用对象
        		
                	
        	垃圾回收算法:
        		1.复制算法:
               在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着
		      进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的
		      年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会
		      被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经
		      被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的
		      “From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重
		     复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
        		2.标记清除算法:
        			标记出所有要回收的对象 等到标记完后再统一清理
        			缺点:容易产生碎片 后续有连续的对象不方便使用
        		3.标记整理算法:
        			标记出所有要回收的对象 让所有存活的对象向一端移动 清理掉边界外的内存
        		4.分代收集算法:
        			新生代:复制算法
        			老年代:标记整理或者标记清除算法
        	
        	垃圾回收器:
            	1.新生代垃圾回收器:
            		①serial:
            			单线程垃圾收集器 在垃圾收集时 用户线程必须停止
            		②parNew:
            			多线程版本的serial 能并行的多线程垃圾回收器
            		③parallel:
            			并行的多线程的垃圾回收器 和parNew类似 侧重点是高吞吐量
            	2.老年代垃圾回收器:
            		①serial old:
            			serial的老年代版本 采用标记整理算法
            		②parallel old:
            			parallel的老年代版本 并行多线程的垃圾回收器 侧重点是吞吐量 采用标记整理算法
            		③CMS:
            			并发标记清除垃圾收集器 采用标记清除算法 适合高响应比的场景
            			步骤:
            	 初始标记:暂停所有用户线程 让gc线程启动 记录下与gcroot相连的对象
            	 并发标记:开启用户和gc线程 gc线程继续在gcroot进行标记 但用户线程继续更新对象的引用域
            	 重新标记:为了修正并发标记期间用户线程继续运行而产生变动的那一部分对象的标记记录
            	 并发清除:用户和gc线程同时开启 gc线程开始在标记区域垃圾回收
            	 	
            	3.新生代老年代都可使用:
            		G1:是唯一可以在老年代和新生代使用的垃圾收集器 使用标记整理算法 性能兼顾相应比和吞吐量
            	它的核心是把内存分为大小一样的region 每一个region都可以作为 eden s0 s1 和 old gen 该垃				圾收集器 会优先回收价值最高的区域 避免了垃圾碎片 最大的好处是化整为零,避免全内存扫描,只					需要按照区域来进行扫描即可 避免了全内存区的GC操作 
	·方法区:
		存放已经被类加载的信息、常量、静态变量、运行时常量池、及时编译器编译后的代码
			运行时常量池:
				字面量:
					字符串的值
					八大基本类型的值
					被声明为final的常量(长度大于Short.MAX_VALUE):static final int k=1;
				符号引用:
					类和方法的全限定名
					字段的名称和描述符
					方法的名称和描述符
		具体实现:
			JDK1.8前:永久代
			JDK1.8后:元空间
			
			永久代:和堆相连接 存放类运行的必要资源 大小固定容易出现内存泄露问题
			元空间:改用本地内存实现方法区 解决了永久代内存泄露的问题 大小一般有本地内存决定
			
			使用原空间代替永久代的原因:
				①字符串类型容易出现内存泄漏问题
				②运行时动态产生的类数量不确定 所以永久代的大小问题难确定 大了浪费空间 小了泄露
				③为了融合jrocket VM 和hotspot实现的虚拟机 ORICAL公司决定取消 因为jrocket没有永久代
		
四.程序计数器:用来记录class字节码文件到了哪一行

五.执行引擎:

六.本地接口:

七.本地方法库:

1.⭐JVM运行时数据区组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1.栈
    ⭐虚拟机栈:(处理方法的调用)
    		Java方法执行的内存模型 每个方法被调用时 都会同步创建一个栈帧 用于存储局部变量表(局部变量)
    		[基本数据类型和对象引用(类的变量名)] 操作数栈 动态连接 方法出入口等 每一个方法被调用直到执行完毕 
    		对应一个栈帧在虚拟机栈中从入栈到出栈的过程
    本地方栈:为JVM使用本地方法服务
    
2.⭐堆(线程共享):存放对象实例和数组(new ),对象实例在此分配内存 GC回收的主要区域
    新生代:
    	EDEN:
		From-Surivor:
         To-Surivor:   
    老年代:
            
3.⭐方法区(线程共享)
    存储 已被JVM加载的类信息(类)、常量、静态变量、即时编译器编译后的代码和⭐运行时常量池
    	运行类常量池:存放编译期 生成的各种 字面量 和 符号引用
    				字面量:文本字符串(String)、8大基本数据类型、final常量
    				符号引用:类和方法的全限定名、字段的名称和描述符、方法名称和描述符
4.程序计数器:记录当前线程所执行到字节码的行数  

2.简述永久代和元空间

1
2
3
4
5
6
7
8
9
10
11
12
13
1.永久代:
	JVM规范中方法区在hotspot的一种实现
	容易出现永久代内存泄露 java8用元空间代替永久代
2.元空间:
	是对JVM规范中方法区的具体实现
	不再和堆连接 存于本地内存中 默认情况原空间可以使用本地内存 为了不让它无限扩张 jvm提供参数限制大小
	元空间的本质和永久代类似 不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,元空间的大小仅受本地内存限制

3.为什么JDK8要用元空间代替永久代?
  Ⅰ.字符串(string类 存在 方法区) 在永久代中 容易出现性能和内存泄漏问题
  Ⅱ.类和方法的信息难确定大小(永久代是指定大小的 大了老年代溢出 小了永久代溢出)
  Ⅲ.永久代为GC带来不必要的麻烦 让回收效率降低
  Ⅳ.Oracle可能将Hotspot和JRockit合二为一 JRockit 没有永久代

3.⭐如何判断对象已经死/什么是垃圾/垃圾回收器的回收的是什么东西?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.引用计数法:
	有地方引用它 计数器+1 引用失效 -1 为0的对象不能被使用 
	使用这个算法是因为它很难解决对象相互循环引用的问题
2.⭐可达性分析法:
	①通过一个叫GC Root的对象作为起点 从这些起点开始向下搜索 节点所走过的路径称为引用链 当一个对象没有引用链时 说明该对象不可用 
	②可作为GC Root对象:
		虚拟机栈的引用对象
		方法区中的静态属性引用的对象
		方法区中常量引用对象
		本地方法的引用对象
	③上述不可用的对象 并非是非死不可 真正一个对象的死亡 至少要经过两次标记过程
	
      不可达对象被第一次标记并且进行一次筛选筛选条件:是否有必要进行finalize方法)
      没有必要执行(对象没有覆盖finalize方法或finalize方法被虚拟机调用过时)
      有必要执行:被放到一个队列中进行第二次标记除非这个对象和引用链上的任何一个对象建立关联 否则会被真的回收
      
      ⭐补充:三色标记法

4.⭐简述强软弱虚引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
强引用:当堆内存不足时候 强引用对象 不会被回收 而是抛出异常

软引用:当内存足够 不会被GC回收 不足时 才被回收

弱引用:被弱引用关联的对象只能生存到下一次 GC回收前、

虚引用:被虚引用关联的对象随时可能被回收


虚引用和软、弱引用的区别:
	①虚引用必须和引用队列一起使用:可以通过判断引用队列是否加入了虚引用 来判断 是否要被垃圾回收器回收
	②软、弱引用是在引用对象被GC回收后 jvm才把加入引用队列中(GC回收后放入引用队列)
	 而 虚引用 则是在引用对象被GC回收前就加入到与之关联的引用队列
	

5.⭐简述垃圾回收算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
垃圾回收:在堆内存中 把死亡的对象回收 释放堆内存空间

1.标记-清除算法:
	标记出所有要回收的对象 标记完成后统一回收所有被标记的对象(效率低 大量不连续的碎片)
2.复制算法:
	在堆内存中把新生代分为 Eden 和 两个survivor
		每次使用Eden和一块survior1
		回收时 把Eden和Survior1中存活的对象 复制到 另一块Survivor2中 最后 清理之前的Eden和Survivor2
		如果存活的大于10% 采用分配担保策略(在第8点中的第2小点) 多出来的直接进入老年代
		分配担保政策:
		Eden 满了——>FromSurvior——>FromSurvior满了——>ToSurvior——>ToSurvior满了——>老年代
3.标记-整理算法:
	标记出所有要回收的对象 然后让所有存活的对象向一端移动 然后清理端边界外的内存

4.分代收集算法:
	综合上面的算法 根据不同分区采用不同的算法
	新生代:有大量的对象死亡 选择复制算法 只需要复制少量的存活对象就可以完成垃圾回收
	老年代:存放大对象 对象存活记录高 选择 标记清除(CMS收集器) 或者 标记整理算法(G1收集器)

6.⭐简述常见的垃圾回收器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
新生代收集器(复制算法):
	Serial:单线程收集器 在进行垃圾收集的时候必须暂停其他所有工作线程
	ParNew:Serial的多线程版本 支持并行操作 在进行垃圾收集的时候可以执行其他工作线程
	Parallel Scavenge:并行的多线程收集器 侧重点的 它的吞吐量 (因为并行 所以吞吐量高)
	
老年代垃圾收集器(标记-清除或标记-整理算法):
	Serial Old: Serial的老年代版本 单线程
	Parallel Old: parallel Scavenge 的老年代版本 多线程 并行 侧重于吞吐量 使用标记整理算法
	CMS:
		同时启用GC和用户线程 
		采用标记清理算法 是一个获取最短回收停顿时间的收集器 高响应比 
		但因为采用标记清除算法 容易产生碎片 
		无法处理浮动的垃圾(用户线程还在继续执行不断产生垃圾只有到下次GC时候才处理)
		低吞吐量(并发标记和并发清除和以用户线程并发执行)

新生代和老年代皆可:
	G1收集器:
		唯一一个可以在新生代和老年代使用的垃圾收集器
		采用标记整理算法 避免碎片
		该收集器 将堆内存分为不同大小相等的region 并维护一个优先列表 每次在允许的时间内 回收价值最大的region
		高并发性 分代收集

7.如何选择垃圾收集器

1
2
3
4
5
6
高吞吐量:
	新生代:Parallel Scavenge 
	老年代:Parallel Old
高响应比:
	新生代:ParNew
	老年代:CMS

⭐8.内存的分配和回收/对象何时进行老年代?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 当Eden分区 没有足够的空间的时候 虚拟机发起一次Minor GC
 
 ·MinorGC 和 MajorGC的区别?
 Minor GC:在新生代发起垃圾回收
 Major GC:在老年代发送垃圾回收
 Full GC:在老年代和新生代发生垃圾回收
 
 ·什么时候进行老年代?
 	①大对象(需要大量连续内存的对象) 直接进入老年代
 	②空间分配担保:
 		当Eden满了 然后 把Eden存活的对象复制到FromSurvior 当FromSurvivor 满了 
 		将存活对象复制到 ToSurvivor  当ToSurvivor 满了 将存活对象送入老年代 
 	③年龄判断:
    	设置一个年龄计数器 每一次GC存活的对象 计数器加一 到了指定的值后 进入老年代

·空间分配担保:
	安全的Minor:老年代中最大的连续可分配空间 大于 新生代 所有对象的空间
	冒险的Minor:老年代中最大的连续可分配空间 大于历代晋生到老年代的平均水平并且允许担保失败 
			   						   小于平均值 则直接进行Full GC 让老年代腾出空间
    	

9.⭐JVM性能监控和故障处理工具

1
2
3
4
5
6
7
JSP:
JSTAT:
JINFO:
JMAP:
JHAT:
JSTACK:
JVISUALVM:

⭐10.Class类文件的组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class类文件的是jvm虚拟机的数据入口
组成:
	1.魔数和主次版本号
	2.常量池:
	3.访问标志:类定义标志
	4.类索引:
	5.符类索引:
	6.接口索引的集合:确定一个类的的继承关系
	7.字段表集合: 定义一个字段的限制条件
		作用范围:public、private
		是否是静态变量:static
		是否可变:final
		并发可见性:volatile
		是否可被序列化:transient
		字段的数据类型:int String
		字段名称:
	8.方法表集合:
	9.属性表:

11.⭐简述JVM中类加载机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
什么是类加载机制:
	JVM把描述类的信息从class文件加载到内存并且对数据进行校验 转化解析和初始化
类加载的全过程:
	加载:
		产生一个二进制数据流 解析这个二进制数据流的静态存储结构转化为方法区的运行时数据结构
        创建一个表示该类型的class类的实例 作为方法区这个类的各种数据入口
	验证:
		为了确保class文件的数据流信息符合当前虚拟机要求 不会危害虚拟机安全
	准备:
		为类变量分配内存并且赋初始值 这些类所使用的内存在方法区进行分配
	解析:
		虚拟机将常量池中的符号引用替换为直接引用
	初始化:
		真正执行java代码的过程 

12.简述JVM中的类加载器和双亲委派模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class类文件是通过类加载器把class文件中的类描述文件加载到内存的

类加载器:
	启动类加载器:
		将存放到\lib目录下的类库加载到虚拟机内存
	拓展加载器:
		将\lib\ext目录下的类库加载到虚拟机内存
	应用程序加载器:
		将用户类路径classpath上所指定的类库加载到虚拟机内存
	自定义加载器:
    	
双亲委派模型:
	当一个类加载器收到类加载请求 先把这个请求委派给父类加载器去完成(所有的请求最终会到顶层的启动类加载器)
	只有当父类加载器完成加载请求 子加载器才回去尝试自己加载

⭐双亲委派模型的打破:
	双亲委派模型可以被打破:
	1.历史原因:
		双亲委派模型出现在类加载器后 在对用户自定义类加载器 双亲委派模型不得不做一些让步 
	2.反向加载:
    	自身的缺陷导致的 如果存在这样一种情况 基础类在把请求交给黑父加载器后 又想调回用户的代码 怎么办?
    	使用上下文类加载器  默认情况下是应用类加载器
    3.结构复杂化
    	

13.静态分配和动态分配

1
2
3
4
5
6
7
8
9
10
11
12
13
静态分配:
	依赖静态类型的定位方法的分配 发生在编译时期 典型的应用是方法的重载
	
动态分配:
	运行时期 根据实际类型来进行方法的分配 发生在程序运行时 应用为方法的重写
	
非虚方法:
	所有 静态 final/private方法 通过invokespecial指令调用 对这个非虚方法的符号引用转为直接引用 在编译完成时 	确定唯一的调用方法

虚方法:
	通过invokevirtual指令调用 会有静态或者动态的分配
	根据编译期方法的接收者和方法的参数的静态类型进行分派
	再根据运行时 方法的调用和实际参数来分派

14.JVM启动模式Client和Server

1
2
3
4
5
6
7
8
9
10
11
12
13
:
	通过 -client 或者 -server 指定
两种模式的区别:
	1.编译器:
		client:轻量级编译器
		server:相对重量级编译器 服务启动后 性能更高
	2.GC:
    	client:新生代(Serial)和老年代(serial old)选择的是串行gc 
    	server:新生代和老年代选择 并行gc
    3.启动方面:
    	client:启动快 编译快 占内存少 优化客户端启动时间
    	server:启动慢 编译完全 效率高 优化服务器的最大化程序执行速度
    	

15.⭐GC的调优

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
调优目标:
	进入老年代的数量降到最低
	减少Full GC的执行时间
	优化JVM的参数:堆栈大小 设计垃圾收集器的模式
优化策略:
	新对象尽可能预留在新生代:
		因为FUllGC的代价比MinorGC高 实际项目中更具日志信息分析新生代空间大小是否分配合理 通过-Xmm设置新代		  大小 最大限度的降低新生代进入老年代
	大对象进入老年代:
		如果大对象首先分配在新生代 会导致空间不足让年龄不够小的对象分配到了老年代 从而频繁的发生FULL GC
		减少FULL-GC的发生次数
	合理的设置进入老年代的对象的年龄:
    	降低FullGC
    设置稳定的堆大小
    以下情况不需要GC优化:
    	MinorGC执行时间小于50ms FullGC不到1s

16.JVM进程有哪些进程启动

1
2
3
4
5
main线程
处理引用的线程
垃圾回收线程
发送分发处理请求给JVM的线程
接收外部命令的线程
本文由作者按照 CC BY 4.0 进行授权