# JVMStudy **Repository Path**: hwxta668/jvmstudy ## Basic Information - **Project Name**: JVMStudy - **Description**: JVM学习代码 参考网站:https://www.bilibili.com/video/BV1yE411Z7AP?p=66&vd_source=b663b486fff8f38ec0d10fa6cc5e56cb - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-07-04 - **Last Updated**: 2023-07-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 参考视频:https://www.bilibili.com/video/BV1yE411Z7AP?p=29&spm_id_from=pageDriver&vd_source=b663b486fff8f38ec0d10fa6cc5e56cb # 什么是JVM? Java Virtual Machine - java 虚拟机,java程序的运行环境(java二进制字节码的运行环境) 好处 - 一次编写到处运行 - 自动内存管理、垃圾回收功能 - 数组下标越界检查 学习JVM有什么用? - 面试 - 了解底层的实现原理 常见的JVM HotSpot 学习路线 1. 内存结构 2. GC垃圾回收 3. 类的字节码结构 4. 类加载器 5. Interpreter解释器和JIT Compiler编译器 内存结构 程序计数器 作用:记住下一条JVM指令的执行地址。 程序的执行流程:程序计数器——>解释器——>机器码——>CPU 物理上的实现是通过寄存器实现“程序计数器” 特点: 1.线程私有的,每个线程都独有一个程序计数器 2.不会存在内存溢出 虚拟机栈 栈:线程运行需要的内存空间 栈帧:每个方法运行时需要的内存 定义 1. 每个线程运行时所需要的内存空间,称为虚拟机栈 2. 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存 3. 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法 -Xss256k :设置栈内存总大小 问题辨析: 1. 垃圾回收是否会涉及栈内存? 2. 栈内存分配越大越好吗? 3. 方法内的局部变量是否线程安全? 1. 栈内存每次使用完毕就会弹出栈,不需要垃圾回收管理 2. 不是。假设给栈设置1M内存,物理内存500M,理论上可以跑500个线程。但是假设给栈内存设置2M内存,物理内存500M,那么理论上只能跑250个线程。所以物理内存固定的时候,栈内存越大,同时能跑的线程就越少。栈内存越大只是能进行更多次的方法递归调用,而不会增强运行的效率,一般采用默认栈内存大小。 3. 不会。因为方法内的局部变量是线程私有的。如果它逃离方法的作用范围(返回值,形参)那么它就是线程不安全的 栈内存溢出 提示:StackOverFlow 1. 栈帧过多导致栈内存溢出 2. 栈帧过大导致栈内存溢出 线程运行诊断 1. CPU占用过多 2. 程序运行时间过长都没有结果 使用工具定位 - 用top定位哪个进程对CPU的占用过高 - ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的CPU占用过高) - jstack 进程 id 本地方法栈 本地方法:不是由java编写的方法,因为java代码有一定限制,不能直接和系统直接交互,所以就需要一些由C或C++编写的本地方法间接的去和系统底层的接口交互。 本地方法栈:本地方法运行时所使用的内存空间 虚拟机栈和本地方法栈都是线程私有的 堆 定义 - 通过new 关键字创建对象都会使用堆内存 - 堆是线程共享的,堆中对象都需要考虑线程安全问题 - 有垃圾回收机制 堆内存溢出 - 提示:——OutOfMemory - -Xmx8m——将堆空间设置为8m 堆内存诊断 工具 1. jps,查看当前系统又哪些java进程 2. jmap,查看堆内存占用情况 jmap - heap 进程id 3. jconsole,有图形界面且多功能的监测工具,可以连续监测 4. jvisualvm,有图形界面且多功能的监测工具,可以保存内存快照 方法区 定义 - 方法区是所有java虚拟机线程共享的区 - 存储类结构(成员变量、方法、构造器、构造方法、运行时常量池) - 虚拟机启动时创建 - 逻辑上是堆的组成部分 - 方法区也会造成内存溢出的错误 - 方法区是规范,元空间或永久代都是它的实现 方法区内存溢出 1.8之前会导致永久代内存溢出 - -XX:MAXPermSize=8m——设置元空间最大容量 - 提示:——OutOfMemoryError:PermGen space 1.8之后会导致元空间内存溢出 - -XX:MAXMetaspaceSize=8m——设置元空间最大容量 - 提示:——OutOfMemoryError:Metaspace 动态生成以及加载这些类的场景 - spring中使用cglib来生成一些代理类 - mybatis使用cglib生成一些代理接口的实现类 运行时常量池 - javap -c xx.class,借助java提供的工具进行反编译 - 二进制字节码(类基本信息、常量池、类方法定义、包含了虚拟机指令) - 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类型、方法名、参数类型、字面量等信息。 - 运行时常量池,常量池是*.class文件中的,当该类被加载时它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址 StringTable 1. 常量池与字符串常量池(串池)的关系 - 常量池中的信息,都会被加载到运行时常量池中 - a,b,ab都是常量池中的符号,只有第一次用到时才会被变为字符串对象并放入StringTable中 - StringTable的结构是hashtable,不能扩容 2. 字符串延迟加载 不会一次性把所有的字符串加入串池,而是每执行一行代码遇到串池中没有的字符串才加入。 3. 字符串变量拼接 字符串的拼接原理是StringBuilder s1,s2,s3是在串池中,s4是在堆中中,所以s4==s3为falses 4. 编译器优化 当要创建一个字符串对象时会先看串池中是否存在,存在则直接引用 5. 可以使用intern方法,主动将串池中还没有的字符串对象放入串池 StringTable位置 从1.7开始StringTable从方法区移动到了堆中 1.6,调整永久代空间 -XX:MaxPermSize=10m 1.8,调整元空间 -Xmx10m,, 1.8中,当98%的时间花在垃圾回收上,但是只回收了2%的堆空间时就会报错 -XX:-UseGCOverheadLimit,不放弃垃圾回收,注意有个“-” StringTable 垃圾回收 StringTable也会发生垃圾回收 StringTable 性能调优 - 调整 -XX:StringTableSize=桶个数 - 考虑将字符串对象是否入池 直接内存 定义 - 常见于NIO操作时,用于数据缓冲区 - 分配回收成本较高,但读写性能高 - 不受JVM内存回收管理 直接内存溢出 提示:OutOfMemoryError:Direct buffer memory 释放原理 - unsafe类、cleaner类 - 使用unsale对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法 - ByteBuffer的实现类内部使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由RefernceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存 -xx:DisableExplicitGC :禁用显示的垃圾回收 垃圾回收 如何判断一个对象可以回收 引用计数法 可达性分析算法 四种引用 垃圾回收算法 标记清除 - 速度较快 - 会造成内存碎片 标记整理 - 速度慢 - 没有内存碎片 复制 - 不会有内存碎片 - 需要占用双倍内存空间 分代垃圾回收 相关VM参数 垃圾回收器 串行 - 单线程 - 堆内存小,适合个人电脑 吞吐量优先 - 多线程 - 堆内存较大,多核CPU - 让单位时间内,STW(垃圾回收)的时间最短(相同时间内垃圾回收次数少) 响应时间优先 - 多线程 - 堆内存较大,多核CPU - 尽可能让单次STW(垃圾回收)的时间最短 G1 - JDK9 默认回收器 - 同时注重吞吐量和低延迟,默认的暂停目标是200ms - 超大堆内存,会将堆划分为多个大小相等的Region - 整体上是标记+整理算法,两个区域之间是复制算法 G1垃圾回收阶段 Young Collection Young Collection+CM Mixed Collection Fulll GC 各个回收器内存不足时 垃圾回收器 新生代 老年代 SerialGC -minor gc -full gc ParallerGC -minor gc -full gc CMS -minor gc G1 -minor gc CMS和G1的老年代内存不足时会发生两种情况 1. 当老年代内存和内存的占比达到45%,触发并发标记和混合收集 2. 产生垃圾的速度大于回收垃圾的速度时,-full GC Young Collection 跨带引用 Remark JDK 8u20 字符串去重 JDK 8u40 并发标记类卸载 JDK 8u60 回收巨型对象 JDK 9 并发标记起始时间的调整 JDK 9 更高效的回收 垃圾回收调优 类加载与字节码