目录
3.运行时常量池 Runtime constant Pool
介绍
jvm基础 、 内存模型 、 gc、 内存异常、内存调优实战案例 、类加载机制、双亲委派模型、 文章上下文 没有强关联性 根据需要读章节即可
jvm内存模型
一、线程私有区域
1. 程序计数器(Program Counter Register)
每个线程独立拥有,记录当前线程执行字节码的指令地
唯一不会出现 outofMemoryError的区域,生命周期与线程一致。
2.虚拟机栈 (JVM Stack)
用于Java方法执行,每个方法对应一个栈顿 (Stack Frame) ,存储局部变量表、操作数栈、动态链接、方法出口等信息
局部变量表大小在编译期确定,栈深度不足会抛出 StackOverfilowError;无法扩展时触发 outofMemoryError
3本地方法栈(Native Method Stack)
与虚拟机栈类似,但服务于Native方法 (如C/C++实现的方法)
部分JVM实现 (如HotSpot) 将虚拟机栈与本地方法栈合并
二、线程共享区域
1.堆Heap
- 最大内存区域,存放对象实例及数组,分为新生代 (Young Generation)和老年代 (Old Generation)
- 新生代: 包含Eden区和两个Survivor区(SO/S1),对象优先分配在Eden,Minor GC后存活对象进入Survivor,多次存活后晋升老年代 。
- 老年代:存放长期存活对象,Major GC (Full GC) 触发时回收 。
- 参数控制:-xms (初始堆大小) 、-xmx (最大堆大小)。
2. 方法区Method Area
- 存储类元数据 (类名、字段、方法》 、常量、静态变量、即时编译器 (JIT) 生成的代码 0 3
- JDK演变:
- JDK7及之前:通过永久代(PermGen) 实现,受-xx:PermSize和-xx:MaxPermSize控制。
- JDK8+:改用元空间 (Metaspace)使用本地内存,大小由-xX:MaxMetaspaceSize 限制
3.运行时常量池 Runtime constant Pool
- ·方法区的一部分,存储编译期生成的字面量(如字符串)和符号引用,支持运行期间动态添加(如 String.intem0)
三、直接内存(Direct Memory)
- 方法区的一部分,存储编译期生成的字面量(如字符串)和符号引用,支持运行期间动态添加(如 Strina.intern() )
四、内存异常与调优
1.常见异常
- outofMemoryError:堆、方法区、直接内存不足时触发
- StackOverflowError :栈深度超过限制时触发
2.调优参数示例
- -xX:MaxTenuringThreshold :控制对象晋升老年代的年龄
- -XX:+UseAdaptiveSizePolicy :动态调整堆各区域比例
五、总结对比
类加载机制
Java类加载机制是JVM将.class文件加载到内存并生成可执行类的核心过程
一、类加载的三大阶段
1. 加载(Loading)
- 通过类的全限定名获取二进制字节流,将静态存储结构转化为方法区的运行时数据结构,并在堆中生成对应的`java.lang.Class`对象作为访问入口
- 数据来源包括本地文件、JAR包、网络资源、动态代理生成等
2. 连接(Linking)
- 验证:确保字节流符合规范,包括文件格式验证(魔数、版本号等)、元数据验证(语义正确性)、字节码验证(逻辑合法性)、符号引用验证(解析可行性)
- 准备:为类变量(`static`修饰)分配内存并设置默认值(如`int`初始化为0),`final static`变量直接赋代码中定义的值
- 解析:将常量池中的符号引用转换为直接引用(如内存地址偏移量)
3. 初始化(Initialization)
- 执行类构造器`<clinit>()`方法,为静态变量显式赋值并执行静态代码块
- 初始化触发条件包括:`new`实例、调用静态方法/字段、反射加载类、初始化子类、启动主类等
二、双亲委派模型
. 1.类加载器层级
- Bootstrap ClassLoader:加载`%JAVA_HOME%/lib`核心类(如`java.lang.*`),由C++实现
- Platform/Extension ClassLoader(Java 9+):加载平台扩展模块或`%JRE_HOME%/ext`目录的类
- Application ClassLoader:加载用户类路径(`-classpath`)的类
2. 委派机制
- 类加载请求优先委派父加载器处理,若父类无法加载(如搜索路径中不存在),子类才尝试加载
- 优点:避免重复加载核心类,保证安全性(如防止自定义`java.lang.String`破坏核心功能)
三、类加载的特殊场景
1. 自定义类加载器
- 继承`ClassLoader`并重写`findClass()`方法,用于加载加密类、热部署、模块化隔离等场景
- 打破双亲委派:如Tomcat为每个Web应用单独使用`WebappClassLoader`,实现类隔离
2. Java 9+的调整
- 引入模块化系统,将扩展类加载器重命名为平台类加载器(`PlatformClassLoader`),支持模块化依赖管理
四、注意事项
1. 主动使用与被动引用
- 访问父类静态字段不会触发子类初始化,但通过数组定义类(如`MyClass[] arr`)不会触发初始化
2. 类卸载条件
- 类的所有实例被回收,且对应的`Class`对象无引用,同时加载该类的`ClassLoader`被回收
通过理解类加载机制,开发者能优化内存管理、实现动态扩展(如插件系统)、规避类冲突问题
****双亲委派模型****
简单理解先找爹再自己干
一、什么事双亲委派模型
当类加载器收到类加载请求时,会先将任务向上委派给父类加载器处理。只有当父类加载器无法完成时,子类加载器才会自己尝试加载,这种机制形成了自下而上的层级委托链,最终由顶层的启动类加载器(Botstrap ClassLoader)作为兜底
二、类加载器的层级结构
1.启动类加载器 (Bootstrap ClassLoader
由 C++实现,负责加载 JAVA_HOME/ib 下的核心类库(如 jar) ,是唯一没有父类的加载器 。
2.扩展类加载器(Extension ClassLoader
加载 JAVA HOME/ib/ext 目录下的扩展类(如JDK 的扩展功能)
3.应用程序类加载器(Application ClassLoader)
加载用户类路径 (ClassPath) 下的类,如项目中的 .class 文件和第三方 Jar 包
三、工作流程(以加载一个用户自定义类为例)
1.应用程序类加载器收到请求,先检查是否已加载过该类
2.向上委派:将请求传递给扩展类加载器
3.扩展类加载器继续向上委派给启动类加载器
4.启动类加载器尝试加载:若成功则返回结果;若失败(如核心库中没有该类),则逐层向下通知子加载器尝试加载
5.最终由应用程序类加载器加载用户类,并缓存结果
四、核心有点
1.安全性:防止核心类库(如 java.lang.string)被篡改。例如,用户自定义的同名类不会被加载,因为父加载器已优先加载了 JDK的核心类
2.避免重复加载: 每个类只加载一次,由父加载器缓存结果,子加载器无需重复处理
3.类隔离性:不同层级的加载器加载的类相互隔离,避免冲突(如 Tomcat 通过自定义加载器隔离不同 Web 应用)
五、通俗比喻
想象一家公司处理问题的方式
- 基层员工 (应用类加载器)遇到问题先向上级汇报
- 中层领导 (扩展类加载器):若解决不了,继续上报给 CEO
- CEO (启动类加载器): 处理重大问题,若无法解决,再逐级向下指派
这种机制保证了核心决策由高层把控,同时基层也能处理个性化需求
通过这种机制,Java 既保证了核心类库的安全,又实现了灵活的动态加载
*自定义类加载器*
1.为什么需要
- 加密保护:防止别人反编译你的代码。例如,将 .cass 文件加密后,只有你的加载器能解密使用
- 非标准来源:比如从网络、数据库加载类,而不是传统的本地文件
- 热更新:不重启程序就能替换已加载的类
2.实现原理
- 继承 classLoader 类,重写 findClass() 方法 (而不是直接改 loadClass() ,避免破坏双亲委派机制)
- 在findclass0 中读取类文件的二进制数据(如解密文件或网络请求),调用 defineClass 将其转为 JVM 可识别的类
3.简单代码逻辑
class MyLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) {
// 1. 从加密文件/网络等读取二进制数据
byte[] bytes = loadClassData(name);
// 2. 解密或处理数据(如果需要)
// 3. 将数据转为类对象
return defineClass(name, bytes, 0, bytes.length);
}
}
典型场景
- 游戏插件:动态加载新功能模块
- 企业应用:不同模块用不同加载器隔离,避免类冲突,
- 代码保护:防止核心代码被反编译
注意事项
- 不要随便破坏双亲委派: 默认机制能保证核心类安全,自定义时尽量只扩展不推翻
- 类隔离:同一个类被不同加载器加载会被视为不同的类 (可能导致类型转换异常)
***JVM 性能调优 案例 实战***
JVM性能调优的核心目标是让 Java程序在合理的内存占用下,减少卡顿 (GC停顿时间)并提高运行效率(吞叶量)
一、调优核心目标 (平衡三要素)
1.内存占用:程序运行时需要的内存大小
堆内存太大可能导致GC时间变长,太小可能频繁触发GC甚至溢出
2.延迟:垃圾回收导致的程序卡顿时间
例如Full GC会暂停所有线程,严重影响用户体验
3.吞吐量:程序运行时间占总时间的比例
高吞吐量意味着程序大部分时间在处理业务而非垃圾回收
二、调优工具 (快速定位问题)
三、调优步骤(六步法)
1.监控问题
通过GC日志(-xloggc:gc.log) 和工具观察:
- 是否频繁Full GC?
- Young区对象是否过快晋升到 old区?
- 内存泄漏迹象 (如老年代占用持续增长)
2.调整参数
- 堆内存:-Xmx4g -Xms4g(最大和初始堆设为相同值,避免动态调整开销)
- 年轻代:-Xmn2g(年轻代大小,建议占堆的1/3~1/2)
- GC算法: 低延迟选G1 (-xx:+UseG1gc),高吞吐选Parallel
3.验证效果
对比调整前后的GC频率、停顿时间 (如原Full GC每次5秒>优化后1秒)
四、常见问题与解决
五、通俗比喻理解
堆内存:像房间,年轻代是 临时储物间(频繁清理),老年代 长期仓库 (很少清理)
GC调优: 调中房间布局,让垃圾 无用对象 即使被清理掉,避免房间堵塞 大扫除 Full GC 耗时
内存泄漏: 像忘关水龙头 水(内存)不断累积,最终益处池子
六、注意事项
1.避免优化过早:先通过日志和工具 确认瓶颈再调整
2.参数逐步调整:每次只改1 2个参数 观察效果
3.关注业务场景: 高并发系统 优先 降低延迟 ,离线计算机系统优先提高吞吐
***java内存泄漏分析:场景及方法***
以生活中“垃圾没扔进垃圾桶"类比,通俗理解内存泄漏:程序中有对象 不再使用,但因被错误引用 无法被回收,最终导致内存资源耗尽
一、常见内存泄漏场景
1.静态集合长期持有对象引用
- 静态容器(如 HashMap、ArayList)的生命周期与程序一致,若存入大量临时对象且未及时清理,会导致无用对象无法释放
- 示例:全局缓存系统未清理过期数据
2.修改集合元素的哈希值 (HashSet/HashMap)
- 若修改 Hashset 中元素的hashcode 或 equas相关字段,会导致元素在哈希表中的存储位置变化,但原位置仍被引用,无法通过remove()) 正确删除
- 解决方法: 避免修改已存入集合的对象的哈希相关字段,或重写hashcodel 和 equals 时保持逻辑稳定
3.单例模式持有外部对象
- 单例对象存储在方法区 (永久代),若其引用了短生命周期对象 (如临时数据),会导致这些对象无法被回收
- 示例:单例类中缓存用户请求数据但未设置 清理策略。
4.未关闭的资源连接
- 数据库连接、网络连接(Socket) 、I0流等未调用 close0 显式关闭,即使对象不再使用,连接仍占用内存
- 解决方法:在finally代码块中关闭资源,或使用 try-with-resources语法
5.监听器与内部类引用
- 添加监听器(如 addxxxListener())后未移除,或非静态内部类隐式持有外部类引用,导致外部类无法回收
- 示例:Activity中注册未取消的广播监听
二、内存泄漏分析方法
1.使用工具检测
- Java自带工具: 通过jmap生成堆转储文件(Heap Dump),用jhat或VisuaNM分析对象引用链,定位未被回收的对象
- ·第三方工具: Eclipse Memory Analyzer (MAT) 、JProfiler等可视化工具,可快速定位泄漏点
2.代码审查关键点
- 检查长生命周期对象 (如静态变量、单例)是否引用了短生命周期对象。
- 确认集合类 (如 Vector 、HashMap) 使用后是否清空或置为 null 。
- 验证 equals0 和 hashCode0)重写是否符合规范 (避免集合操作异常)
3.模拟压力测试
- 长时间运行程序或重复执行特定操作,观察内存占用是否持续增长 (如 outofMemoryError提示)
三、总结与规避建议
- 核心原则:确保无用对象不被长生命周期对象引用
- 编码习惯:及时释放资源、避免静态集合滥用、谨慎使用单例.
- 工具辅助:结合 VisualVM 监控内存变化:,定期进行堆转储分析
**GC日志解读 实战 案例**
一、GC日志核心字段
1.时间相关指标
时间戳:如 5.141:Gc pause 表示JVM启动后5.141秒发生GC。
新停时间: Total time for which application threads were stopped: 0.0782 seconds 表示STW (全局停顿)总耗时
2.内存变化
各区域容量:如 Eden区 (年轻代) >Survivor区>老年代 的内存占用变化
回收效果:Freed 1024K(10%) 表示本次回收释放的内存比例
3.GC类型
Minor GC:回收年轻代,日志中通常标记为 gc 或 Young gc
Full GC:回收整个堆,日志中明确标注 Full gc,需重点关注其频率和耗时
二、常见问题排查方法
场景1:频繁Full GC
日志特征: 短时间内多次出现 Full gc,且老年代回收后内存无明显释放
可能原因:
- 内存泄漏:对象无法回收,老年代逐渐占满。通过jmap -histo 分析对象分布
- 大对象分配:直接进入老年代,触发Full GC
场景2:年轻代回收效率低
日志特征:Minor gc 后存活对象过多,频繁晋升到老年代 (如Premature Promotion)
解决方案:
调整-xx:SurvivorRatio 增加 Survivor 区比例
检查对象生命周期,避免短期对象长期存活,
场景3:吞吐量不足
日志特征:Throughput(应用运行时间占比)低于90%
优化方向:
增加堆内存(-xmx)
更换GC算法 (如G1优化停顿时间)
三、工具辅助分析
1.GCViewer (本地工具)
可视化展示内存变化、GC频率、吞吐量等 。
支持对比不同GC日志,快速定位异常时段。
2.GCeasy (在线平台
上传日志自动生成报告,标注潜在问题 (如内存泄漏、长暂停)
3.JVM内置命令
jstat -gcutil实时监控各区域使用率
jmap -dump生成堆快照,结合MAT分析对象引用链
四、实战案例
问题描述:应用运行一段时间后卡顿,日志显示 Full Gc 耗时1秒以上。
分析步骤:
1.用GCViewer打开日志,发现Full GC频率每小时超过5次
2. jmap -histo发现某个缓存类实例数异常增长
3.检查代码发现缓存未设置过期策略,修复后Full GC频率降至每天1次
***GC垃圾回收算法及应用案例***
一、核心垃圾回收算法
1.标记-清除算法(Mark-Sweep)
原理:分为标记和清除两阶段。标记阶段通过可达性分析识别存活对象,清除阶段回收未被标记的对象。
缺点:产生内存碎片,影响大对象分配效率;需两次扫描,效率较低
应用案例: 老年代垃圾回收 (如 CMS收集器的初始标记和并发标记阶段)
2.复制算法(Copying)
原理:将内存分为两块(如Eden和Survivor区),每次仅使用一块。垃圾回收时,将存活对象复制到另一块内存,清空原区域。
优点:无内存碎片,适合对象存活率低的场景
应用案例: 新生代垃圾回收 (YGC/Minor GC) ,如Serial、ParNew收集器
3.标记整理算法 (Mark-Compact)
原理:标记存活对象后,将其向内存一端移动,清除边界外的空间。
优点:避免内存碎片,适合对象存活率高的场景。
应用案例:老年代垃圾回收 (如Serial Old、Parallel Old收集器)
4.分代收集算法 (Generational Collection)
原理:将堆分为新生代和老年代,针对不同区域特性选择算法:
新生代:存活率低,采用复制算法 (如Eden区到Survivor区)
老年代:存活率高,采用标记-清除或标记-整理算法。
应用案例: HotSpot虚拟机默认策略,如Parallel Scavenge (新生代) 与Parallel Old (老年代)组合
二、应用案例与典型场景
1.CMS收集器 (Concurrent Mark Sweep)
- 算法:基于标记-清除,通过并发标记减少停顿时间
- 场景: 适用于对延迟敏感的老年代回收 (如Web服务)
- 参数:-XX:+UseConcMarkSweepGC
2.G1收集器 (Garbage-First)
- 算法: 分区 (Region)管理,结合标记-整理和复制算法,预测停顿时间.
- 场景:大内存(>4GB)、高吞吐与低延迟兼顾 (如大数据应用)
- 参数:-XX:+UseG1GC
3. ZGC与Shenandoah
- 算法:基于染色指针和读屏障,实现亚毫秒级停顿 (需JDK11+)
- 场景:超低延迟要求的实时系统 (如金融交易) )
三、配置与使用指南
1.选择垃圾收集器
- 串行收集器 (单线程)(适合客户端应用)-XX:+UseSerialGC
- 并行收集器 (多线程)XX:+UseParallelGC(默认,适合吞吐优先场景)
- G1收集器XX:+UseG1GC(平衡吞吐与延迟)
2.调整堆与分代比例
- 设置堆大小:-Xms(初始堆)-Xmx(最大)
- 新生代与老年代比例:(老年代是新生代的2倍)-XX:NewRatio=2
3.优化GC日志与监控
- 启用日志:-Xlog:gc*(JDK9+) 或-XX:+PrintGCDetails
- 工具分析:JVisualVM、GCViewer解析日志,优化停顿时间与吞吐量
总结
- Java GC算法的选择需结合应用场景
- 高吞吐: Parallel Scavenge/Old.
- 低延迟:CMS、G1或ZGC。
- 大内存:优先G1或ZGC
jvm基础
一、基本定义与作用
1.虚拟计算机架构
JVM是一个虚构的计算机系统。通过软件模拟硬件功能,屏蔽底层操作系统差异,实现Java程序*“一次编译,到处运行”**的特性
2.核心功能
字节码执行:将.class 文件中的字节码转换为机器码执行
内存管理: 自动分配和回收内存 (堆、栈等) ,避免手动管理导致的内存泄漏 跨平台支持: 不同操作系统只需安装对应JVM版本,即可运行同一Java程序
二、核心架构组成
1.类加载子系统 (Class Loader)
负责加载 class 文件,完成类的加载、链接(验证、准备、解析)和初始化,形成可被JVM直接使用的Java类型
2.运行时数据区
- 堆(Heap) : 存储所有对象实例和数组,线程共享,是垃圾回收的主要区域
- 方法区 (Method Area) : 存储类结构信息 (如类名、方法代码) 、静态变量和常量池
- 虚拟机栈 (JVM Stack): 线程私有,存储局部变量、方法调用栈帧,方法执行时入栈,结束则出栈
- 程序计数器 (PC Register) : 记录当前线程执行的字节码指令地址,确保多线程切换后能恢复执行
- 本地方法栈 (Native Method Stack) : 服务于JVM调用的Native方法(如C/C++库)
3.执行引擎
解释器:逐行解释字节码为机器码,启动速度快但执行效率低,
即时编译器 (JIT) : 将热点代码编译为本地机器码,提升执行效率
垃圾回收器 (GC): 自动回收堆内存中不再使用的对象,通过分代算法(新生代、老年代)优化回收效率
三、运行机制
1.类加载流程
- 按需加载类文件,过程包括加载>验证>准备->解析 -初始化。例如,首次使用 new关键字时触发类加载
2.线程执行模型
- 每个线程独立拥有程序计数器、虚拟机栈和本地方法栈,共享堆和方法区,通过线程调度实现并发
3.跨平台实现
- Java源码编译为与平台无关的字节码 (.class文件),由JVM根据当前操作系统动态转换为本地机器码执行
四、核心特点
1.自动内存管理
通过垃圾回收机制自动释放无用对象内存,开发者无需手动干预,但需避免内存泄漏(如长生命周期对象持有短生命周期引用).
2.安全机制
提供字节码校验、类加载验证和安全管理器,防止恶意代码破坏系统
3.高性能优化
JIT编译器与自适应优化技术 (如热点代码检测)结合,平衡启动速度与运行效率
五、常见JVM 实现
HotSpot: Oracle官方默认虚拟机,广泛用于生产环境,支持高效的JIT编译和垃圾回收
GraaIVM: 支持多语言 (Java、Python等)的高性能虚拟机,适用于云原生场景
***************持续更新 易经的方式看技术*************************
***************持续更新 易经的方式看技术*************************
***************持续更新 易经的方式看技术*************************