- class类文件结构
- 类加载机制
- jvm运行时数据区
一、Class文件结构
二、类加载机制
class的声明周期
1、加载
1、根据全限定名获取二进制字节流
1 | Class.forName("com.mysql.jdbc.Driver"); |
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 | protected Class<?> loadClass(String name, boolean resolve) |
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、方法区(元空间)
各个线程的共享存储区域,用于存储:
- 已被虚拟机加载的类信息
- 常量 final
- 静态变量 static
- 即时编译器编译后的代码
热点代码:代码段被反复调用次数超过阈值(10000)。基于采样 和 计数
会出现的异常:OutOfMemory。原因:常量池撑爆
CPU
- 操作栈
- 操作寄存器
2、栈和栈帧
栈帧
局部变量表(本地变量表)
原子性协议:32位的机器(一个slot可以存放一个32位以内的数据类型),long是在并发更新的时候不是线程安全的
long、double 分为两个连续的slot空间,高低位存储
在32位机器上会跳出循环,在64位机器上不会
操作数栈
动态连接
类似于 “多态,反射”,只有在运行时才能 把 符号引用 --> 直接引用
其他大多数情况,在解释时 就确定了 符号引用 --> 直接引用
方法返回连接
帧数据区
false,true
栈默认大小:-Xss108K;
栈的调用深度 由 栈帧的大小 和 栈的大小 共同决定;栈帧大小不是确定的,所以栈的深度也是动态的
3、程序计数器
每条机器指令都有自己的地址
CPU从指令寄存器中获取要执行的指令,获取完后,清空指令寄存器;
程序计数器中存放下一条要执行的指令地址,指令寄存器为空后,将自己指向的指令放入指令寄存器中,同时,自己指向下一条指令;
五、字节码执行引擎
函数如何调用
符号引用 –> 直接引用
重写,重载
方法调用:
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
中,对象的生命周期包括以下几个阶段:
- 创建阶段(
Created
)- 应用阶段(
In Use
)- 不可见阶段(
Invisible
)- 不可达阶段(
Unreachable
)- 收集阶段(
Collected
)- 终结阶段(
Finalized
)- 对象空间重分配阶段(
De-allocated
)
四种引用
1)、强引用
类似 Object a = new Object();
只要强引用存在,垃圾收集器永远不会回收它
jvm 在内存不够时,会抛出OutOfMemory
使用完以后a = null;
才会被回收
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出
OutOfM moryError
错误,使程序异常终止,也不会靠随意回收具有强引用 对象来解决内存不足的问题
2)、软引用SoftReference
软引用是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常
1 | Object obj = new 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 |
标记清除算法
分为两个阶段 “标记” 和 “清除”,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象;
两个不足:
一个是效率问题,标记和清除效率都不高
另一个是空间问题,标记清除之后产生大量不连续的内存碎片
标记整理算法
标记阶段仍然和标记清除算法一样,
复制算法
效率最高
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
标记清除算法
优点:减小停顿时间,提高用户体验
缺陷:
- 对CPU特别敏感,cpu越少对程序影响越大。CMS默认的回收线程数 = (cpu核心数量 + 3)/4,进一法
- CMS 和 浮动垃圾,标记和清理阶段,用户线程仍在运行
-XX:CMSInitatingOccupancyFraction=68
,当老年代达到68%时,判断(100-68)的空间是否够用户线程使用:如果够则触发CMS垃圾回收;如果剩余内存不够用户线程运行,则会抛出Concurrent Mode Failure
错误,触发SerialOld回收(停顿时间过长) - 碎片整理 — FullGC
-XX:UseCMSCompactAtFullCollection
整理
碎片整理过程没法并发,通过SerialOld进行
G1收集器
Humongous:超大对象
G1最大可使用32G
整体上看是 标记整理算法,局部是复制算法
MaxGCPauseMillis 最大GC停顿时间
RememberSet
开启参数:-XX:+UseG1GC
垃圾回收机制
空间担保
FullGC的触发条件
七、jvm性能调优
jps
1 | jps -mlv |
jstat
1 | jstat -class pid x y |
打印GC失败原因
1 | jstat -gccause pid x y |
jinfo
1 | jinfo -flag pid |
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 处理引用线程(软引用/弱引用/虚引用)的垃圾回收问题
实现调优
调优的原则
- 合理编写代码
文件句柄
网络 - 合理利用硬件资源
- 合理的进行调优
终极规则:降低FGC的频次,减少GC的时间
大对象导出
- 百万级
- 7~8W记录
- result
- for() => 本地没有测试
慢sql
- tomcat 每隔三个月必然爆一次,tomcat有非常多的hprof, -XX:HeapDumpOnOutOfMemoryError
- 32G
- 重启工程师
- 数据库设计有问题
- 一张表:上千万,更多, -> 分库
分表 冷热数据 很多冗余(空间换时间)
- 日志:亿