JVM基础


  1. class类文件结构
  2. 类加载机制
  3. jvm运行时数据区

一、Class文件结构

二、类加载机制

class的声明周期

1、加载

1、根据全限定名获取二进制字节流

1
2
3
Class.forName("com.mysql.jdbc.Driver");

Applet : 通过网络读取 class文件

2、把字节流代表的静态存储结构(class文件) 转化为 方法区的运行时数据结构

3、Java堆上生成一个代表该类的对象

​ Class 类 ==> 元数据区(方法区)==> 蓝图

​ Object对象 ==> 根据蓝图做成的 成品 Kclass

2、验证

1、魔数:cafebabe

2、主版本号、次版本号

3、常量池:常量池里的常量是否有不被支持的类型

4、验证常量池中的值是否存在

3、准备

准备阶段是准备内存的过程,初始化为0

4、解析

5、初始化

jdk编译默认添加构造函数

clinit class init: 类的初始化

有static 变量 才有 clinit

clinit 父类 静态变量, 父类静态块,子类静态变量,子类静态块

init():V 父类的变量初始化,父类的构造方法,

6、使用

7、卸载

三、类加载器

8、ClassLoader

类加载器的默认机制:双亲委派机制

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
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

C:/Program Files/Java/jdk1.8.0_60/jre/lib/rt.jar!/sun/misc/Launcher.class

BootstrapClassLoader 加载 System.getProperty(“sun.boot.class.path”); 路径下的class

ExtClassLoader 加载 System.getProperty(“java.ext.dirs”); 路径下的class

AppClassLoader 加载 System.getProperty(“java.class.path”); 路径下的class

使用双亲委派的原因:

保护jdk的基类

9、Tomcat打破了双亲委派机制

自定义类加载器,重写 loadClass,就能打破双亲委派

判断两个Class是否相等,

false,false,true,false

即使是同一个class文件,使用不同的ClassLoader加载进来,那么它就是不同的 class

9、Class.forName

四、JVM运行时数据区

JVM运行时数据结构

1、方法区(元空间)

各个线程的共享存储区域,用于存储:

  1. 已被虚拟机加载的类信息
  2. 常量 final
  3. 静态变量 static
  4. 即时编译器编译后的代码

热点代码:代码段被反复调用次数超过阈值(10000)。基于采样 和 计数

会出现的异常:OutOfMemory。原因:常量池撑爆

CPU

  1. 操作栈
  2. 操作寄存器

2、栈和栈帧

栈帧

局部变量表(本地变量表)

原子性协议:32位的机器(一个slot可以存放一个32位以内的数据类型),long是在并发更新的时候不是线程安全的

long、double 分为两个连续的slot空间,高低位存储

在32位机器上会跳出循环,在64位机器上不会

操作数栈

动态连接

类似于 “多态,反射”,只有在运行时才能 把 符号引用 --> 直接引用

其他大多数情况,在解释时 就确定了 符号引用 --> 直接引用

方法返回连接

帧数据区

false,true

栈默认大小:-Xss108K;

栈的调用深度 由 栈帧的大小 和 栈的大小 共同决定;栈帧大小不是确定的,所以栈的深度也是动态的

3、程序计数器

每条机器指令都有自己的地址

  1. CPU从指令寄存器中获取要执行的指令,获取完后,清空指令寄存器;

  2. 程序计数器中存放下一条要执行的指令地址,指令寄存器为空后,将自己指向的指令放入指令寄存器中,同时,自己指向下一条指令;

五、字节码执行引擎

函数如何调用

符号引用 –> 直接引用

重写,重载

方法调用:

1、静态分派

函数调用,对应的字节码指令:

0xb6 invokevirtual 调用实例方法

0xb7 invokespecial 调用超类构建方法, 实例初始化方法, 私有方法

0xb8 invokestatic 调用静态方法

0xb9 invokeinterface 调用接口方法

0xba invokedynamic 调用动态方法

在编译期确定,并且是根据静态类型来确定函数调用版本。可以反编译class文件,看到实际调用方法。

函数overload

2、动态分派

不是根据静态类型来确定,而是在运行时根据实际类型来确定函数的版本

函数override

方法表

虚拟机动态分配机制

虚方法表(vtable)存在方法区上

单分派与多分派

静态多分派(方法重载),动态单分派(方法重写)

3、字节码

操作码

iload

istore

iadd

isub

imul

idiv

操作数

六、垃圾回收

垃圾回收:方法区 和 堆区 空间不够用,OutOfMemoryError

加载类:空间不够用(类信息)

堆区:空间不够用(分配实例)

垃圾回收的要点知识

引用计数法

给对象添加一个引用计数器。每当有一个地方引用这个对象,这个计数器就加1;每当引用失效,这个计数器就减1;当计数器为0的时候就代表该对象不能再被使用;

可达性分析算法

GC Root对象

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;

  • 方法区中 类静态属性引用的对象;

  • 方法区中常量引用的对象;

  • 本地方法栈JNI(即一般说的Native方法)引用的对象;

GC关心的东西:这块数据是不是一个指针

对象的生命周期

Java中,对象的生命周期包括以下几个阶段:

  1. 创建阶段(Created)
  2. 应用阶段(In Use)
  3. 不可见阶段(Invisible)
  4. 不可达阶段(Unreachable)
  5. 收集阶段(Collected)
  6. 终结阶段(Finalized)
  7. 对象空间重分配阶段(De-allocated)

四种引用

1)、强引用

类似 Object a = new Object();只要强引用存在,垃圾收集器永远不会回收它

jvm 在内存不够时,会抛出OutOfMemory

使用完以后a = null;才会被回收

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfM moryError错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题

2)、软引用SoftReference

软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常

1
2
3
4
Object obj = new Object();
SoftReference softRef = new SoftReference(obj); // 将软引用指向 Object实例
obj = null; // 结束 obj 对 Object实例的强引用
Object anotherRef = (Object) softRef.get(); // 重新将 anotherRef 强引用指向 Object实例

3)、弱引用 WeekReference

弱引用也是用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。

4)、虚引用 PhanReference

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。

jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。

GC最重要的几件事

1、哪些内存要回收?

GCRoot 里面没有引用的对象

​ 代码不断变化,如果让GC Root不变 => stop the world

2、什么时候回收

安全点

3、如何回收

oopMap 存 对象位置,类型

垃圾回收算法

System.gc(); 手动执行垃圾回收。程序告诉GC收集器执行回收动作,不过到底什么时候执行GC

属于FullGC

有哪些缺陷? STW(stop the world)

1
2


标记清除算法

分为两个阶段 “标记” 和 “清除”,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象;

两个不足:

  • 一个是效率问题,标记和清除效率都不高

  • 另一个是空间问题,标记清除之后产生大量不连续的内存碎片

标记整理算法

标记阶段仍然和标记清除算法一样,

复制算法

效率最高

MinorGC:Eden-> S0/S1

MajorGC:Old区 内部收集算法

FullGC:Eden + S0/S1 -> Old,永久代回收,都是FullGC

现在商业虚拟机的垃圾收集都采用“分代收集算法”。不同分区使用不同的收集算法,达到最高的使用几率

安全点Safepoint

safeRegion

oopMap

JDK垃圾回收器

-XX:+Use[Name]]GC

Serial收集器

开启参数: -XX:+UseSerialGC

复制算法

单线程的收集器,它的“单线程”的意义并不仅仅说明它会使用一个CPU或者一条收集线程去完成垃圾收集工作,最重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束

SerialOld收集器

复制算法

ParNew收集器

开启参数:-XX:UseParNewGC

复制算法

Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其他行为包括Serial收集器可用的所有控制参数,收集算法,STW,对象分配规则,回收策略 与 Serial收集器完全一样

Parallel Scavenge收集器

开启参数:-XX:UseParallelGC

3个参数选一个设置即可

Parallel Old收集器

Concurrent Mark Sweep收集器

开启参数:-XX:ConcMarkSweepGC

标记清除算法

优点:减小停顿时间,提高用户体验

缺陷:

  1. 对CPU特别敏感,cpu越少对程序影响越大。CMS默认的回收线程数 = (cpu核心数量 + 3)/4,进一法
  2. CMS 和 浮动垃圾,标记和清理阶段,用户线程仍在运行
    -XX:CMSInitatingOccupancyFraction=68,当老年代达到68%时,判断(100-68)的空间是否够用户线程使用:如果够则触发CMS垃圾回收;如果剩余内存不够用户线程运行,则会抛出Concurrent Mode Failure错误,触发SerialOld回收(停顿时间过长)
  3. 碎片整理 — FullGC
    -XX:UseCMSCompactAtFullCollection整理
    碎片整理过程没法并发,通过SerialOld进行

G1收集器

Humongous:超大对象

G1最大可使用32G

整体上看是 标记整理算法,局部是复制算法

MaxGCPauseMillis 最大GC停顿时间

RememberSet

开启参数:-XX:+UseG1GC

垃圾回收机制

空间担保

FullGC的触发条件

七、jvm性能调优

jps

1
jps -mlv

jstat

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
jstat -class pid x y
jstat -compiler pid x y
jstat -gc pid x y
S0C S0 Capacity S0容量大小
S1C S1 Capacity S1容量大小
S0U S0 Used S0已使用容量大小
S1U S1 Used S1已使用容量大小
EC Eden Capacity Eden容量大小
EU Eden Used Eden已使用容量大小
OC Old Capacity Old容量大小
OU Old Used Old已使用容量大小
MC Metaspace Capacity Metaspace容量大小
MU Metaspace Capacity Metaspace已使用空间大小
CCSC 压缩类 容量大小
CCSU 压缩类 已使用容量大小
YGC YoungGC 次数
YGCT YoungGC 累计时间
FGC FullGC 次数
FGCT FullGC 累计时间
GCT GC 时间

jstat -gccapacity pid x y
NGCMN (New Generation Capacity min)新生代最小容量
NGCMX
NGC (New Generation Capacity)当前新生代容量
OGCMN
OGCMX
OGC (Old Generation Capacity)当前老年代容量
OC (Old Generation Capacity)老年代大小

MCMN (Metaspace Generation Capacity)当前元空间最小
MCMX

jstat -gcnew pid x y
TT Tenuring
MTT
DC

jstat -gcold pid x y
PC Perm Capacity
PU Perm Used

jstat -gcoldcapacity pid x y

jstat -gcmetacapacity pid x y

jstat -gcutil pid x y
jstat -gccause pid x y

打印GC失败原因

1
2
jstat -gccause pid x y
LGCC Last Gc Cause 最后一次GC原因

jinfo

1
2
3
jinfo -flag pid
jinfo -flag +PrintGCDetails pid 如果程序运行时没有打印gc日志,可以指定打印
jinfo -flag -PrintGCDetails pid 如果程序运行时已经打印了gc日志,可以禁止打印

jvm标准参数

java -help, 不会随着jdk版本变化

jvm非标准参数

java -X

java -XX

jmap

jmap -histo:live pid

jmap pid > pid.hprof

jstack

jstack pid

线程状态

基础线程

Finalizer线程 finaliner() 放在一个FQueue里,有一个守护线程负责清理FQueue中的

Monitor Ctrl-Break 监听Ctrl+C 中断任务

Attach Listener 接收外部命令,执行该命令

Signal Dispatcher 分发外部命令

Reference Handler 处理引用线程(软引用/弱引用/虚引用)的垃圾回收问题

实现调优

调优的原则

  1. 合理编写代码
    文件句柄
    网络
  2. 合理利用硬件资源
  3. 合理的进行调优

终极规则:降低FGC的频次,减少GC的时间

大对象导出

  • 百万级
  • 7~8W记录
  • result
  • for() => 本地没有测试

慢sql

  • tomcat 每隔三个月必然爆一次,tomcat有非常多的hprof, -XX:HeapDumpOnOutOfMemoryError
  • 32G
  • 重启工程师
  • 数据库设计有问题
  • 一张表:上千万,更多, -> 分库
    分表
    冷热数据
    很多冗余(空间换时间)
  • 日志:亿