文章目录
- 1, JVM整体结构详解
- 2, Java代码编译和执行过程
- 3, 内存管理
- 4, 垃圾回收
- 4.1, 垃圾收集算法
- 4.2, 对象引用类型
- 4.3, 垃圾回收算法
- 4.4, 垃圾回收触发时间
1, JVM整体结构详解
2, Java代码编译和执行过程
编译流程
类加载器
- Bootstrap ClassLoader: 用来加载jdk中特定的jar包, rt.jar: runtime, 运行时环境jar包
- Extension ClassLoader: 用来加载扩展类jar包
- App ClassLoader: 加载应用程序的jar包, 即应用程序真正有用的类加载器
- Custom ClassLoader: 自定义的加载器, 用于加载指定的java文件
加载流程
- Loading: 类的信息从文件中获取并载入到JVM的内存中
- Verifying: 验证, 检测读入的结构是否符合JVM规范的描述, 不符合不加载
- Preparing: 分配一个结构存储类的所有信息
- Resolving: 把类的常量池中的所有符号引用改变成直接引用
- Initializing: 初始化, 执行静态初始化程序, 把静态变量初始化成指定的值
3, 内存管理
Java栈区
作用: 存放Java执行时的所有数据
组成: 由栈帧组成, 一个栈帧代表一个方法的执行
栈帧:
- 每个方法从调用到执行完成 就 对应一个栈帧在 虚拟机栈中
入栈
到出栈
– 比如: a方法在运行调用b方法时, java虚拟机就会创建一个保存b方法的栈帧, 然后把这个栈帧压入到java栈区中, 当b方法执行完成返回到a方法时, 这个栈帧则会随之弹出java栈区
- 栈帧包含: 局部变量表, 栈操作数, 动态链接, 方法出口
– 应用中常见的栈溢出(stack overflow)异常, 即当栈的深度大于JVM所允许的最大深度时就会抛出该异常. 一个没有退出方法的递归元素即可产生该异常.
本地方法栈
作用: 即专门为Native方法服务
它与栈区一样也是通过栈帧记录方法的调用
方法区
存储被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后等数据, 并且永远占据内存
堆区
作用: 所有通过new常见的对象的内存都在堆中分配
特点: 它是虚拟机中最大的一块内存, 是GC要回收的部分
堆区内存图:
- Young Generation: 表示新生代区
- Old Generation: 表示老年代区
- 区别
– 刚创建的对象全部放入Young Generation内存区, 当Young Generation内存区不足时java虚拟机通过一定的规则将Young区的对应移到Old Generation内存区, 使Young Generation足够空间提供给其他创建的对象, 当YoungGeneration和OldGeneration都没有足够内存时JVM会抛出OutMemary异常, 并且是垃圾回收器重点回收的两个区域
- 为什么分为新生代区和老生代区两部分而不是一个完整区域:
– 方便开发人员动态调整两个区域的大小, 比如当开发聊天类应用时新创建的message较多, 可以增大新生代区的内存减小老生代区内存, 便于加快对象的创建; 而开发大型服务类程序并不需要频繁创建对象时可以适当减小新生代区而增加老生代区, 达到对象常驻内存包装服务的稳定性.
4, 垃圾回收
4.1, 垃圾收集算法
引用计数算法:
该算法是java虚拟机最早(1.2版本以前)使用的算法
当在内存中创建一个对象时会对该对象生成一个引用计数器, 同时将引用计数器+1, 每当有新引用引用到该对象时引用器累计+1, 当其中一个引用销毁时引用计数器-1, 当引用器减为0时表示该对象已沦为垃圾对象, 可被回收
引用计数器最大的一个缺陷
是当两个对象相互引用
并未被其他引用引用时它们的计数器不会为0, 但他们已经是垃圾了, 却不能被回收
可达性算法
从jdk1.2以后都是用的该算法, 也叫根搜索算法, 即把程序中的引用关系看着一张图
, 从根节点(GC root)开始寻找对应的引用子节点, 再从子节点遍历下去, 当所有引用节点遍历完成后, 剩余未被遍历到的节点被视为不可达的节点
, 并被视为垃圾对象
4.2, 对象引用类型
- 强引用, 软引用, 弱引用, 虚引用
- 最常使用的是
强引用
和弱引用
弱引用的创建:
Object obj = new Object();
WeakReference<Object> wr = new WeakReference<Object>(obj);
obj = null;
wr.get();
4.3, 垃圾回收算法
标记-清除算法
从上图可看到, 从根集合(根节点)开始遍历引用, 遍历到A再到C, 此时B未被引用, 然后扫描B对象并被标记为可回收对象, 当垃圾回收对象执行时会把B清除掉
优点: 不需要进行对象的移动, 并且仅对不存活的对象进行处理, 在存活对象比较多的情况下极为高效
缺点: 由于标记-清除算法直接回收不存活的对象, 因此会造成内存碎片, 不利于后续对象分配
复制算法
从根集合开始遍历, 当遍历到A引用可达是便把A引用复制到另一块空闲内存中,继续往下遍历发现B不可达便跳过, 然后遍历下一个引用, 只要可达的引用都复制到之前开辟的可达内存区域, 当遍历完整棵树时便把原来的引用内存清空, 保留赋值后的内存空间. 这样达到内存回收
优点: 当存活的对象比较少时极为高效
缺点: 成本大, 需要额为分配一块内存作为交换空间进行对象移动
标记-整理算法
从根集合开始遍历, 通过扫描整个内存区中可回收对象并标记为可回收对象, 最后清除垃圾对象并把后面的引用向前移动.
该算法是在标记-清除
算法上的改进, 在回收不存活的的对象占用空间时, 将所有存活对象往左端移动并更新对应指针, 成本更好, 但解决了碎片化问题
以上三种算法各有优缺点, 在虚拟机中会结合不同的场景使用不同的算法
4.4, 垃圾回收触发时间
- java虚拟机无法再为新的对象分配内存空间了
- 手动调用System.gc()方法(强烈不推荐)
–手动调用虚拟机并不会立即去调用垃圾回收
- 低优先级的GC线程, 被运行时就会执行GC