JVM

是什么#

Java Virtual Machine 翻译为Java虚拟机

为什么#

  • 提供了JAVA程序的运行环境,保证了JAVA运行与平台无关的特性

  • 定义了JVM规范,保证了JAVA代码的统一性和规范性

  • JVM在运行期间提供了内存分配和垃圾回收的作用

工作流程#

类加载#

加载#

先通过类的全类名获取到类的二进制文件,封装二进制文件到运行时常量池,再在堆中创建Class对象,用于封装运行时常量池中的数据结构,并对外提供访问接口

类加载器

分类

  • 启动类加载器

    主要加载核心类,例如以java.开头的jar包

  • 扩展类加载器

    主要用于加载具有扩展功能的jar包

  • 应用程序类加载器

    主要用于加载ClassPath目录下的jar包

  • 自定义类加载

机制

  • 双亲委派机制

    是什么?

    • 在请求类加载时,类加载器接收到请求,并不会直接进行加载,而是会先将这个请求传递给父类加载器,一直传递到启动类加载器,再由启动类加载器判断能否加载该类,若是不行,则将请求原路向下传递,直到碰到了可以加载的类加载器为止

    为什么?

    • 防止一个JVM中出现多个同类名相等的类的情况

      • 问题:同一JVM中可以存在俩个String类吗?

        不行,因为存在双亲委派机制,每次类加载的时候会先由启动类加载器加载,而String属于核心类,即使String类多次加载,每一次到启动类加载器,都会被判断加载过,不会再次加载

  • 破坏双亲委派机制

    是什么?

    双亲委派问题:例如,DriverManager类在使用时需要使用到Driver类,而DriverManager类是由启动类加载器加载的,而Driver实现类是由应用程序类加载器加载的,所以DriverManager无法直接调用Driver实现类

    解决:为了解决类似的问题,Java虚拟机提出使用线程上下文类加载器,例如当DriverManager类需要调用Driver实现类时,就使用线程上下文类加载器来获取Driver实现类,这样就是破坏双亲委派机制

    为什么?

    双亲委派机制缺点:子类加载器加载的类可以访问父类加载器加载的类,但是父类加载器加载的类是无法访问子类加载器加载的类

    原因:双亲委派机制和类加载器的命名空间

连接#

  • 验证

    验证Class对象是否符合规范

  • 准备

    为静态变量分配内存

  • 解析

    将符号引用解析为直接引用

初始化#

可以说,初始化阶段是执行类构造器 < clinit > 方法的过程。首先说下类构造器 < clinit > 方法和实例构造器 < init > 方法有什么区别。< clinit > 方法是在类加载的初始化阶段执行,是对静态变量、静态代码块进行的初始化。而< init > 方法是new一个对象,即调用类的 constructor方法时才会执行,是对非静态变量进行的初始化

类被加载之后并不会立刻进行初始化,只有当类首次被主动引用时才会进行初始化

主动引用

  • 使用new创建对象
  • 使用反射创建对象
  • 调用类属性/类方法
  • JVM启动类

被动引用

  • 定义对象数组
  • 调用类常量

引申:对象加载#

对象创建过程#

  • 先判断对象对应的类是否被加载,需要先进行类加载

  • 内存分配

  • 初始化默认值

  • 设置对象头

    对象头中包含了是否充当锁,HashCode值,GC年龄等

  • 执行初始化方法(init方法)

对象初始化过程#

  • 初始化父类中的静态成员变量和静态代码块
  • 初始化子类中的静态成员变量和静态代码块
    • 以上这俩个步骤执行于类加载阶段
  • 初始化父类的普通成员变量和代码块,再执行父类的构造方法
  • 初始化子类的普通成员变量和代码块,再执行子类的构造方法
    • 以上这俩个步骤执行性于对象创建过程

JVM运行时结构#

线程共享#

  • 堆主要用于存储对象,数组

  • 堆中包含新生代,老年代和eden区,占用内存比例为8:1:1

    新对象会在新生代,大对象和长期存活的对象会存储在老年代

方法区

  • 在JDK7中,方法区的具体实现是永久代

    在JDK8中,方法区的具体实现为元空间,元空间会直接占用计算机内存,但是在逻辑上属于JVM

  • 方法区中包含了常量池,字符串常量池被迁移到了堆中,但是运行时常量池仍然在方法区

线程私有#

栈空间中有栈帧,每次调用方法都会使用栈帧封装方法的主要信息,包含方法名,参数,局部变量等等,方法执行完毕,进行出栈

程序计数器

程序计数器主要用于存储计算机下一个指令的地址

垃圾回收#

垃圾回收算法#

新生代

复制算法

老年代

  • 标记清除法
  • 标记整理法

垃圾回收器#

串行

GC单线程内存回收, 暂停所有用户线程

并行

多个线程进行GC, 暂停所有的用户线程

  • JDK8默认使用并行垃圾回收器

    • 缺点:造成STW

    • 优点:内存占用小,数据吞吐量大

并发

用户线程和GC线程同时执行(不一定是并行, 可能是交替执行), 不需要停顿用户线程(停顿时间很短)

  • CMS
  • G1

调优#

调优就是找平衡点

调优目的#

  • 让老年代的对象尽可能少

  • 让GC次数减少,时间减少

  • 解决内存泄漏

调优步骤#

  • 监控JVM状态
  • 分析结果
  • 进行调整
  • 重复监控

调优方案#

  • 减少创建对象的数量
  • 减少使用全局变量和大对象 ( 很可能会进入老年代 )
  • 调整新生代和老年代的大小到最合适
  • 选择合适的GC收集器
posted @   互联网农民工  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示