理解
JVM结构
JVM 执行顺序 : class文件->类装载器 - >内存区->执行引擎
类装载器采用parent模型(每个加载请求会不断往父类传,最终到最顶层,最顶层实现不了,再一层层往下检测哪层能实现)避免父子加载器重复加载,主要包含,java内部库加载器->java扩展类库加载器->程序员代码类加载器->程序员自定义的类加载器
其中内存区主要包含 方法区 虚拟机栈 本地方法栈 堆 程序计数器
内存分为静态内存(方法区,栈)和动态内存(堆),静态内存回收方式是固定的,GC指的是动态内存,即堆中的内存
虚拟机栈和本地方法栈的区别是本地方法栈是为java native方法服务的,虚拟机栈是为程序员写的方法服务的
流程
当我们编写的class文件进入类装载器后,java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是线程私有的。
方法调用=》创建栈帧-》压入虚拟机栈=》方法执行完毕,栈帧出栈并被销毁,栈帧保存了局部变量表、操作数栈、动态链接、方法出口等数据,栈线程隔离
对JVM内存进行设置和分析可以定位异常代码,防止内存溢出,强引用(内存溢出也不回收,报错)、软引用(内存溢出回收)、弱引用(GC时回收)和虚引用(随时可以回收),不同的类型对应不同的GC机制,代码执行过程就是一个压栈入栈的过程,栈保存基本类型和引用类型,堆保存对象,引用变量就是对象的首地址
内存模型
堆中有主内存
每个线程维护一个自己的本地内存,维护堆内存中的共享变量的副本,各个线程从自己的本地内存中存取数据
普通代码不能保证可见性(线程操作本地内存后主存立刻更新,其他本地内存立刻同步)
使用volatile关键字,可以禁止代码指令优化重排序,和强制访问主存,保证可见性,但是不能保证原子性
GC
检测算法
- 引用计数法
给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1,没引用回收
问题:两个对象互相引用,永远无法回收,
- 可达性分析算法
从根集结点检测,没引用的回收,根集一般包括java栈中引用的对象、方法区常良池中引用的对象
收集算法
-
标记-清除
先标记要回收的,然后把标记的回收,会造成内存碎片 -
复制
用2倍的内存,遍历区域1,把正在引用的复制到内存2,遍历结束后回收区域1的全部内存,无内存碎片,耗内存
- 标记-整理
先标记要回收的,然后边回收边整理内存
- 分代收集
堆内存分 年轻代(又分为8:1:1的三个子区域,用标记-清除) 年老代(满了用fullGC,不满用复制) 永生代(程序关闭才回收)
一个对象在年轻代出生,每经过n检测未被回收,即移动到下一代。不同代采取不同回收方式
n 是人为设置和由不同代内存大小决定的
调优
减少 fullGC 和 gc频率,使用JConsole(内存、CPU曲线等)、Java VisualVM、jsatck(通过线程PID查询,堆栈内存等),DUMP堆文件分析,等工具检测
- 修改各个内存区域的大小和比例
- 检测内存曲线防止死锁,死循环等异常代码