JVM

本文内容:

  1. 一次编译到处运行
  2. Java对象
  3. Java内存模型(JMM)
  4. GC
  5. JVM参数

一次编译到处运行

JVM屏蔽了底层的区别

java如何编译成字节码?

  • 解析与填充符号表
    • 词法分析、语法分析
    • 填充符号表(KV对,标识符与它的声明类型、作用域、地址等)
  • 注解处理       
  • 语义分析与字节码生成
    • 标注检查:
      • 变量声明
      • 变量与赋值的类型匹配
    • 数据及控制流分析:      
      • 局部变量使用前是否赋值
      • 每条路径是否都有返回值
      • 受检异常是否都处理  
  • 解语法糖
  • 字节码生成

Class文件结构

先明确一点,Class是由java源码编译而来,所以它必定包含了类中的所有信息,包含

  • 类名、类修饰符、继承的类、实现的接口
  • 字段名、字段类型、字段修饰符
  • 方法名、方法的修饰符、方法引用的类型(形参、返回类型)、方法内部的具体代码
  • 字面量

所以我们要做的就是把它们以一种结构化的方式组织起来,Class的结构概览:

  • 魔数(简单判别是否是Class文件),版本号
  • 常量池:字面量、符号引用(类名、接口名、字段名、方法名、变量名)
  • 标志:类修饰符
  • 类自身的引用,父类的引用,接口的引用
  • 字段表,方法表,属性表

需要注意的是常量池会被其他部分频繁引用,因为它存储了所有的名,字段、方法、类都需要一个名,所以包含一个指向池中常量的指针

类加载过程

输入:class的二进制字节流,输出:方法区的Class对象,整个过程的几个阶段以及阶段任务如下图

  • 加载:将字节码文件的二进制字节流加载到JVM中,开发者可自定义类加载器(重写loadClass())来决定从哪加载
    • 主要做什么:
      • 将二进制字节流加载到JVM中
      • 将其代表的静态结构转化为方法区的运行时数据结构
      • 实例化一个Class对象,作为访问这些数据的外部接口
    • 几种情况对类主动引用的情况必须要立刻加载class文件:
      • 实例化对象、引用类变量、类方法、内部静态类
      • main的类
      • 加载子类时,父类要先被加载
      • 反射调用类时
  • 验证:检验字节码的安全性,防止对JVM造成危害
    • 文件格式验证
      • 是否以魔数开头
      • 版本号是否可以处理
      • UTF8常量中是否有不符合UTF8编码的数据
      • ...
    • 元数据验证(类级别的语法)
      • 是否继承了类
      • 是否实现了抽象父类中的所有方法
      • 是否继承了final类型的类
      • ...
    • 字节码验证(方法体)
      • 是否有不合法的强制类型转化(父类转子类)
      • 跳转指令不会跳到方法体以外的指令上
      • 数据类型和指令配合无误,不会用加载long的指令来加载int
      • ...
    • 符号引用验证
      • 符号引用中的可访问性是否能被当前类访问
      • 类符号引用能否找到类
      • 指定类中是否存在 指定名和描述符的 方法和字段
      • ...
  • 准备:对类变量分配内存、赋零值(数据类型的默认值)
  • 解析:符号引用(字面量,如类的全限定名、方法描述符) -> 直接引用(内存地址、偏移量)
    • 类/接口的解析
      • 假定当前代码位于类D处,要将一个符合引用N解析成类C
      • 非数组,交给D的类加载器去加载
      • 数组,其元素类型交给D的类加载器加载,JVM生成一个代表数组维度和元素的对象
      • 符号引用验证,确定D可以访问C
    • 字段解析(子类和父类的同名字段,拒绝编译)
      • 假定字段属于类C,先加载C
      • 在C中查找是否有符号 该字段的简单名称、描述,有就返回其直接引用
      • 递归在实现的接口中(自下向上)查找
      • 递归在继承的类中查找
      • 报异常
    • 类方法解析
      • 加载类C,如果符号引用的是接口,则报异常
      • 在类C中查找是否符合 方法名、描述符的,有则返回直接引用
      • 在继承体系上递归向上查找
      • 在实现接口中递归向上查找
      • 报错,找到成功后要进行权限验证,看是否可访问
    • 接口方法解析
      • 加载接口I,如果符合引用的是类,则报异常
      • 在I中查找是否符合 方法名、描述符的,有则返回直接引用
      • 在继承体系上递归向上查找
      • 报错,接口方法一定是public的,不用进行权限验证 
  • 初始化:执行类构造函数cinit函数

Java对象

new一个对象的过程

  • new一个对象,分配内存
    • 指针碰撞:空间整理型回收器,移动指针
    • 空闲列表:复制型,分配一个空闲的内存块
    • 并发情况:CAS 或 TLAB本地线程分配缓冲(堆是线程共享的,分配对象空间时需要进行同步,给每个线程建立一个独享的空间,在上面分配)
  • 执行构造函数,进行初始化
  • 使用
  • 回收资源
  • GC

注:Java创建子类对象时,不会创建父类对象,只是调用父类对象的构造来进行初始化(继承了父类的成员) 

栈上分配

  • 线程私有的对象,减小GC
  • 逃逸分析 
    • 如果对象仅在方法体内使用
    • -XX:DoEscapeAnalysis
  • 标量替换
    • 作用:如果未用到对象的全部成员,可以节约内存
    • 做法:用一些基本类型的局部变量替换对象
    • -XX:EliminateAllocations

对象内存布局

对象头:指向类型的指针、运行时信息(hashcode,GC年龄,锁信息),数组还有长度信息

实例数据

填充对齐:HotSpot要求对象必须是8byte的整数倍

访问定位

句柄:

 直接指针:

对比

 句柄直接引用
优点GC对象移动时不需要修改栈中引用(对栈操作比较麻烦,对线程空间进行修改--需要再写回内存)1次内存访问
缺点2次内存访问对象变化时要需要修改栈中引用

Java内存模型(JMM)

GC

判断对象是否需要回收:引用计数、可达性分析,从GCRoot(当前栈中对象、全局对象、常量)出发,形成引用链,未经过的即为需要回收的对象

GCROOT:栈中引用的对象、方法区类静态成员引用的对象、方法区常量引用的对象

G1对比CMS

 G1CMS
 优先收集,空间分为Region,维护每个Region回收时间、价值,以便实现可预测的停顿

Concurrent Mark Sweep

并发、标记清理

 并行,可充分利用多核CPU优势

 

 

空间碎片局部是基于复制,整体是基于标记-整理(复制时往某一边复制),无碎片

 

有碎片

缺点 收集阶段用户线程继续工作,产生浮动垃圾,预留空间不够时导致Concurrent Mode Failure,引起Full GC
 
  • 初始标记(STD)
  • 并发标记
  • 最终标记(STD)
  • 筛选回收(STD)
  • 初始标记(STD)
  • 并发标记
  • 重写标记(STD)
  • 并发清除
选择可预测的停顿时间、并行吞吐量

 

 GC调优

  • 目标:保证最大停顿时间,尽可能提高吞吐量
  • 做法:
    • 分析GC日志,计算 吞吐量、最大停顿时间
    • 查找FullGC出现原因,避免FullGC,减小MinorGC  
    • 根据场景选择GC回收器、设定合适的GC器参数 + JVM参数    

Minor和FullGC的区别:

  • Minor只针对young,当引用到old时就停止扫描
  • FullGC针对全局,扫描量大
  • Minor基于复制

 JVM参数:

  • -Xmx: maximun heap size
  • -Xms:最小堆大小
  • -Xmn: new 新生代
  • -xss: 内存栈初始大小
  • -XX:NewRatio 新手代:老年代
  •  -XX:SurvivorRatio=4: Eden:survivor
  • 持久代(类信息)

内存泄露:

  • 静态集合内的对象,长期对象持有短期对象的引用
  • 各种连接没close

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值