jvm初探
Table of Contents
jvm
逃逸分析
- 栈上分配
- 同步省略 对方法内部的无意义synchronized优化
- 标量替换 对仅在某类内部使用的静态类,对象,如果其完全由基本类型构成,直接替换为基本类型
public static class Data {
int uid;
int pid;
}
public void test(){
Data data = new Data();
}
//等价于
public void test(){
int uid = 0;
int pid = 0;
}
对象创建
- 判断对象的类是否加载,链接,初始化 jvm执行new 指令,首先检查指令的参数能否在metaspace的常量池中定位到一个类的符号引用,并检查类是否被加载,解析和初始化.(即判断类的元信息是否存在),如果无,就在双亲委派模式下,使用当前类的加载器以 ClassLoader+包名+类名为key查找class文件,如果找不到,抛出classNotFoundException异常,找到就进行类加载
- 为对象分配内存 cas保证并发,或者TLAB提前解决,计算对象占用空间,在heap中划分内存,引用变量的话,4个byte
- 如果内存规整,jvm采用指针碰撞(bump the pointer)分配内存,用一个指针表示使用过的内存和未使用的内存的分界线,分配对象内存,移动指针就行
- 如果内存不规整,jvm维护空闲列表(Free List),记录那些内存块可用,分配一个足够的内存快划分给对象实例
- 内存是否规整,也取决与gc算法
- 初始化类信息
- 设置对象头
- hashcode,gc的信息,锁信息.etc,jvm实现
JIT
热点探测
hotspot采用基于计数器的热点探测,为每个方法建立2个不同类型的计数器,方法调用计数器(Invocation counter),server模式是10000,和回边计数器(back edge counter),后者统计循环体执行的循环次数
通过 -XX:CompoileThresold
流程:
- 方法调用前先检查是否被JIT编译过
- 如果没有,方法调用计数器+1,检查是否大于阈值,大于提交JIT编译请求,然后解释器执行
- 如果存在,执行编译后的机器码
热度衰减
方法调用计数器统计的次数不是绝对次数,是相对执行频率,超过一定时间达不到调用次数,计数器衰减一半,这段时间称为半衰周期(count half life time)
通过 -XX:UseCounterDecay
关闭热度衰减
配置
模式
- -Xint 纯解释器
- -Xcomp 纯JIT
- -Xmied 混合 -最好
- -client c1
- -server c2
GC
标记
- 引用计数
为每个对象保存一个整形的引用计数属性,无法处理
循环引用
- 可达性分析 以根对象GC roots为起始点,按照从上至下的方式搜索gcroots连接的目标对象是否可达,执行后,搜索走过的路径称为引用链reference chain,目标对象无任何Rc,视为不可达 Gc roots对象
- jvm的栈引用对象
- 本地方法栈内JNI的引用对象
- meta space的类静态属性引用对象
- meta space的常量引用对象
- 所有被synchronized持有对象
- jvm内部引用
- JMXBean,JVMTI中注册的回调,本地代码缓存.etc jdk8之前,Object存在finalize方法,也就是类销毁之前的操作,这给了一个类的复活可能,但是jdk9之后已经@Deprected了,可以想象到的是不正确的操作会导致gc严重的性能问题
清除
- 标记清除 注意此时和标记阶段无关,此算法依然要执行标记,标记阶段是标记可达对象,清除阶段是对堆内存从头到尾线性遍历,如果发现某个对象未被标记为可达,回收,将对象地址保存在空闲列表内,效率不高,会产生大量内存碎片
- 复制 2倍内存,将所有存活对象复制到新region,避免了内存碎片,无标记清除过程,Gc需要维护对象的引用关系,原因是java的对象引用是直接引用,并且此过程是复制不是移动,此算法主要用于存活对象较少,垃圾对象较多时,edn s1 s2
- 标记压缩 一阶段和标记清除相同,二阶段将存活对象压缩到内存一端,需要STW