JVM基础知识

本文参考链接

运行时数据区

  • 堆:存放java对象实例,GC回收的地方,也称GC堆
  • 方法区:堆的逻辑部分,存放静态变量,常量,类信息
  • 虚拟机栈:存放运行时的java方法,局部变量,(栈帧形式)
  • 本地方法栈:类似虚拟机栈,服务的时native方法
  • 程序计数器:线程切换,异常处理,指示下一行执行的字节码序号

虚拟机栈:

栈的FILO符合java方法间的调用

栈帧:

每个方法在执行的时候会创建一个栈帧,栈帧是虚拟机栈的栈元素,是虚拟机用于进行方法调用和方法执行的数据结构;

  • 操作数栈:字节码指令的写入和读取,用于运算和参数传递
  • 局部变量表:用于存放参数和方法内部定义的局部变量
  • 动态连接:没看懂
  • 方法出口:方法返回的字节码指令,返回到方法被调用的位置;

当一个方法被执行时,会创建一个栈帧(当前栈帧),这个栈帧处于栈顶,在编译时期,栈帧中需要的操作数栈和局部变量表的大小已经完全确定,栈帧的内存不会在运行时受到影响;

  • 方法中的参数和局部变量会存放到局部变量表中,当这个方法是非static方法时,局部变量表中的第一个数据是这个方法的实例对象;如果局部变量是一个对象实例,就会在局部变量表中以reference引用出现,上图的obj就是一个reference引用,它的真是指向是堆中的Object对象;
  • 操作数栈:先将num入栈,再将1入栈,取出栈顶的两个元素,相加再入栈,此时栈中只剩下num+1了;再出栈,将num+1这个数赋值给局部变量表中的num;上面的操作就是num = num + 1的实现;
动态连接:
  • 将虚方法的符号引用转为直接引用:
    在类加载的解析阶段,会将运行时常量池的符号引用转换为直接引用,这个这个操作叫做静态解析,但是它只会解析非虚方法(静态方法,私有方法,构造方法,final修饰方法)的符号引用,而栈帧中的动态连接就是在运行时将其余的符号引用转换为直接引用;
  • 分派(静态分派,动态分派):选择多态的执行版本
      Person person = new Man();

在上述代码中,Person类是person对象的静态类型,Man类是person对象的实际类型,静态类型和实际类型都可以发生改变,通过强转改变静态类型,重新new一个对象改变实际类型,所以静态类型的确定是在编译期,实际类型的确定在代码运行时;
重载是通过静态类型来判断执行版本,这个称为静态分派
重写是通过实际类型来判断执行版本,这个称为动态分派

栈帧数据的通信:

方法和方法之间的数据交互是通过使用同一块内存完成的,即栈帧A的本地变量表可能是栈帧B的操作数栈,同一块内存对于不同的栈帧是不同的角色;

虚拟机对于栈的优化:方法内联

方法A调用方法B,将方法B的代码作为方法A的一部分来执行,从而避免创建栈帧,减少栈内存的消耗

类加载过程

深入理解Java虚拟机插图

上面的类加载过程中,加载,验证,准备,初始化,卸载,这五个步骤的顺序是确定的,必须按照这个顺序进行,解析阶段在一些情况下可以在初始化之后进行;

加载:

  • 通过类的全限定名获取定义这个类的二进制字节流
  • 将字节流代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个这个类的class对象,作为方法区的访问入口;

获取二进制字节流这一步是开发者在类加载中可控性最强的阶段(通过自定义classLoader)

验证(连接第一步):

目的:确保Class文件的字节流符合当前虚拟机的要求,不会对虚拟机的安全产生威胁;class字节码文件不一定通过.java源文件编译来,(动态代理原理中是通过特定接口生成,还可以通过网络<Applet>,其他文件<JSP>等方式生成class字节码),所以,通过其他途径生成的字节码文件,可以做java语言做不到的事情,这些可能导致系统的奔溃;

验证内容:
  • 文件格式验证:字节流是否符合Class文件格式规范
  • 元数据验证:字节码内容的元数据信息是否符合java语言规范
  • 字节码验证:校验类的方法体在运行是会不会危害虚拟机
  • 符号引用验证:对类自身以外的信息校验

准备(连接第二步):

为类的静态变量分配内存(方法区),并且初始化静态变量为0/false/null;

注意:这里是将static变量分配到方法区,而不是实例变量,实例变量是在对象创建的时候分配在中的;

解析(连接第三步):

将常量池中的符号引用转为直接引用

  • 符号引用:一组描述引用目标的符号,存放在Class文件的常量池中,符号引用和虚拟机实现的内存布局无关,如果不经过转换无法得到真正的内存地址;

  • 直接引用:能直接指向目标的指针或间接定位到目标的句柄,直接引用和虚拟机实现的内存布局相关,能直接找到内存中的目标;
    https://cloud.tencent.com/developer/article/1450501

常量池种类:

  1. class文件常量池:编译后,class常量池存放 字面量符号引用
  2. 全局字符串常量池:类加载阶段,在堆中创建字符串对象,对象的真实引用放入全局常量池,1.7以后,全局字符串常量池被移动到了堆内;
  3. 运行时常量池:类加载阶段(二进制字节码代表的静态数据结构转为方法区的运行时数据结构),将class常量池的符号引用放入运行时常量池, 解析阶段,将运行时常量池的符号引用转为和全局常量池一致的直接引用
  4. 基本类型包装类对象常量池:

初始化:

执行类构造器clinit(),初始化类变量和其他资源,在准备阶段,我们为类变量在方法区分配了内存,并且将类变量置为0,false,/null ,在初始化阶段会将类变量设置为代码中的实际值,如果类中有静态代码块也会在这一阶段执行;

注意:
  • clinit()是由编译器自动收集的所有类变量赋值行为和静态代码块中的语句合并产生;
  • 虚拟机保证子类的clinit()执行前,一定会先执行父类的clinit(),所以第一个执行的clinit()一定是Object类的;
  • 如果一个类没有赋值操作和静态代码块,那就没有必要执行clinit(),虚拟机就不会执行
  • 接口执行clinit()前不需要执行父类的clinit(),接口的实现类在初始化时也不会执行接口的clinit()
  • 虚拟机保证clinit()执行时的线程安全(加锁,其他线程阻塞,释放锁)

对象创建过程

  • 设置对象:对对象进行必要的设置,设置HashCode,GC分代年龄等
  • init方法:初始化实例变量的值(代码中设置的值)

对象内存布局

  • 对其填充其实是一个占位符没有特别的意义,对象大小必须是8字节的整数倍,通过对其填充来确保是8的整数倍;

对象访问

句柄访问:
直接访问:

可达性分析法——判断对象是否需要回收

可达性分析法:

  • 可达性分析:如果一个对象到GCRoot对象没有引用链,则表明不可达;
GCRoot对象:
  • Java虚拟机栈中的引用对象
  • 本地方法栈中JNI(Native)引用的对象
  • 方法区中引用的常量静态属性对象
public class OOM {
    
    private int rootConstant = 1000; // 方法区常量对象GCRoot
    private static Object rootStatic = new Object(); // 方法区静态对象GCRoot
    private Object nonRoot = new Object(); // 类实例对象,非GCRoot

    public void test (){
        Object rootJavaStack = new Object();  //  虚拟机栈引用的对象GCRoot

        // 可达,不会回收
        Object obj0 = rootConstant;
        Object obj1 = rootStatic;
        Object obj2 = rootJavaStack;
        // 不可达,回收
        Object obj3 = nonRoot;
    }
    
}
Java引用类型:
  • 强引用 StrongReference:普通引用
  • 软引用 SoftReference:内存不足时会回收软引用对象(展示图片使用内存)
  • 弱引用 WeakReference:放生GC时会回收弱引用对象(ThreadLocal的Entry使用内存)
  • 虚引用 PhantomReference:最弱的引用(日常开发用不到)
生存还是死亡:

其实在被认定了不可达以后,还需要经过两次标记筛选;

  • 一次标记:如果一个对象到GCRoot没有引用,则需要进行一次标记:是否有必要执行finalize(),当对象没有覆盖finalize()或者finalize()已经被虚拟机执行过,则表明没有必要执行;
  • 二次标记:如果对象有必要执行finalize(),则进行二次标记:将对象放入一个F-Queue队列,虚拟机创建一个优先级特别低的线程Finalizer执行队列的对象的finalize方法,在执行前会再次判断这个对象是否有到GCroot的引用链,如果还没有,则真正回收;

垃圾回收算法

复制算法:

复制算法
特点:
  • 只能使用一半内存
  • 没有内存碎片
  • 需要复制内存

标记清除:

标记清除算法
特点:
  • 效率低
  • 会出现内存碎片

标记整理:

标记整理算法
特点:
  • 步骤较多
  • 不会出现内存碎片

分代收集算法:

将内存分为新时代和老年代,新生代再分为Eden和Survivor(from to)区


  • 对象优先在Eden区分配
  • 大对象直接进入老年代
  • 长期存活的对象进入老年代:在Eden区中经过一次Minor GC仍然存活就转移到Survivor区,在对象头中存放的GC年龄+1,每经过一次Minor GC,From和To就会发生一次复制,对象的年龄就会+1,默认情况下,一个对象经过15此Minor GC仍然存活就移动到老年代;
  • 动态对象年龄判定:如果在Survivor区中的同一年龄的对象的内存和大小大于Survivor(From/To)内存的一半,就直接移动到老年代;

GC发生的条件:空间不够(Eden/老年代满了,继续放对象时,会触发GC)

收集行为 算法 特点 发生时刻
新生代 Minor GC 复制算法 回收频率高,对象存活率低 Eden区无可分配内存
老年代 Full GC 标记整理/ 标记清除 回收频率低,对象存活率高 老年代无可分配内存

垃圾收集器()
吞吐量 = 业务线程运行时间/总CPU时间

常见的七种垃圾收集器
垃圾收集器 收集器类型 收集对象 算法
Serial 单线程 新生代 复制
ParNew 并行的多线程 新生代 复制
Parallel Scavenge 并行的多线程 新生代 复制
Serial Old 单线程 老年代 标记整理
Parallel Old 并行的多线程 老年代 标记整理
CMS 并行并发的收集器 老年代 标记清除
G1 并发并行的收集器 新生代&老年代 复制+标记整理
单线程垃圾收集器工作示意图
多线程垃圾收集器工作示意图
CMS垃圾收集器:Concurrent Mark Sweep
步骤:
  • 初始标记Stop the world
  • 并发标记
  • 重新标记Stop the world
  • 并发清除
CMS收集器工作示意图
特点:
  • 最短停顿回收时间:耗时最长并发标记和并发清除可以同用户线程一起运行,总体上可以说CMS收集器是同用户线程一起并发执行的
  • 降低总吞吐量:并发标记和并发清除会占用CPU资源,降低吞吐量
  • 无法清除浮动垃圾:在并发清除时用户线程产生的垃圾称为 "浮动垃圾"
  • 堆内存不规整:标记清除会导致堆内存不规整;
G1收集器:Garbage First

最新、技术最前沿的垃圾收集器

步骤:
  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收
G1收集器工作示意图
特点:
  • 不降低吞吐量的情况下减少停顿时间:可预测停顿

  • 分代收集:可以回收新生代和老年代

  • 不会产生内存碎片

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345