![](https://img-blog.csdnimg.cn/cdc750e0f4a8479f91ea876ace09c1af.png?x-oss-process=image/resize,m_fixed,h_224,w_224)
JVM
文章平均质量分 88
JVM笔记
-代号9527
逢山开路,遇水搭桥!纸上得来终觉浅,绝知此事要躬行。
展开
-
JVM面试
字符串常量池的回收逻辑和对象的回收逻辑类似,内存不足的情况下,如果字符串常量池中的常量不被使用就可以被回收,而方法区中回收的是类的元信息,逻辑更复杂一些。移动到堆之后,就可以利用对象的垃圾回收器,对字符串常量池进行回收。:溢出之后会抛出OutOfMemoryError,JDK7及之前提示永久代,JDK8及之后提示元空间(方法区存放的内容,如类的元信息超过了方法区空间的最大值)内存溢出指的是内存中某一块区域的使用量超过了允许使用的最大值,从而使用内存时因空间不足而失败,虚拟机一般会抛出指定的错误。原创 2024-05-09 19:46:25 · 1000 阅读 · 0 评论 -
【进阶篇】五、Java Agent实现系统数据采集
APM系统用到了Java Agent的静态代理模式 + 字节码增强,从而采集到方法的耗时、数据库的查询时长、SQL信息等Arthas用到了Java Agent的动态代理模式,用到了JMX获取到一些信息,以及字节码增强打印方法耗时、参数等。原创 2024-04-17 17:40:17 · 1431 阅读 · 0 评论 -
【进阶篇】四、字节码增强框架:ASM、ByteBuddy
相比自己的代码里用Spring AOP添加某些功能,字节码增强更适配无侵入式的Java Agent场景。比如下面写个Java Agent打印。原创 2024-04-16 20:49:28 · 1190 阅读 · 0 评论 -
【进阶篇】三、Java Agent实现自定义Arthas工具
JDK1.5起,提供Java Management Extensions (JMX) 技术,JMX技术使得开发者可以在内存中保存一个MXbean对象,存一些配置信息(类似对象容器的方式去存放一种特有的对象),另外,JVM也将一些程序的运行信息放入了MXbean对象。使用jd-core,copy官方示例,Loader注意改字节码的来源,Printer重写end方法,打印反编译后的源码即可。用JMX的方法,还是通过对应的MXBean来获取。思路:内存中存的是类的字节码信息,用。原创 2024-04-12 21:16:57 · 1505 阅读 · 0 评论 -
【进阶篇】二、实现Java Agent的静态加载和动态加载
通过Java Agent,生成一种特殊的jar包(一种工具),业务程序可以主动去调用jar包里的方法。如此,Java Agent的jar就会被执行。,适用于Arthas类似的诊断工具。实现动态加载模式,需要在Java Agent项目中编写一个。如此,JVM将会加载agent中的代码去执行。在Java Agent的项目中编写一个。原创 2024-04-12 14:04:50 · 856 阅读 · 0 评论 -
【进阶篇】一、GraalVM的安装与整合SpringBoot3
AOT编译器会为特定的平台创建可执行文件(如windows下的exe),这种文件即Native Image(本地镜像),如此,就不再具备跨平台性。总之,GraalVM在JIT模式,使用Graal编译器,性能好,但内存CPU占用不低。如果既追求低资源占用,又追求高性能,就花钱买企业版的GraalVM。最后,单论性能,社区版的GraalVM本地镜像模式性能是不如Hotspot JVM的JIT模式的。最后,跑在容器里的话,Dockefile可参考:(注意基础镜像以及生成本地镜像)原创 2024-04-12 09:00:00 · 1585 阅读 · 0 评论 -
【开发篇】十七、基准测试框架JMH
判断一个方法的耗时 ⇒ endTime-startTime ⇒ 不准确,首先部分对象懒加载,第一次请求会慢一些,其次,程序运行时,JIT即时编译器会实时优化代码,如随着执行次数的增加,程序性能逐渐优化:⇒ JMH(Java Microbenchmark Harness)准确的测方法执行的性能。原创 2024-04-11 15:12:33 · 939 阅读 · 0 评论 -
【开发篇】十六、线程耗尽与死锁检测
Jmeter并发调一下上面两个模拟死锁的接口,再调其他正常的接口,也不能正常响应和返回了。因为tomcat线程池里设置的500个线程被耗尽了。程序在启动运行一段时间之后,就无法接受任何请求了。一般用于开发环境或者本地复现,生产环境无连接权限。方法三:使用fastthread自动检测线程问题。方法一:线程转储,搜索deadlock。查看详情,找到死锁发生的行号。方法二:Visual VM。原创 2024-04-11 13:22:01 · 179 阅读 · 0 评论 -
【开发篇】十五、火焰图、trace定位接口响应时间长
背景:接口A响应时间很长,需要定位到是哪一个方法有性能问题。原创 2024-04-11 10:20:48 · 426 阅读 · 0 评论 -
【开发篇】十四、线程信息转储 + CPU高占用案例
性能问题优化:⇒。原创 2024-04-10 18:25:38 · 302 阅读 · 0 评论 -
【开发篇】十三、JVM基础参数设置与垃圾回收器的选择
GC问题解决方式:-Xmx设置最大堆内存(max),-Xms设置可用堆内存大(total)计算理论最大可用堆空间,如服务器内存4G,操作系统自己使用的内存+元空间最大值+其它软件占用1.5G ⇒ 理论最大可用堆空间为2.5g,只是理论值。(减去元空间是因为,Java 8及以后,元空间使用的是直接内存)最后设置的堆内存大小,应是按照系统最大并发估计,且小于上面的理论值。最后,将-Xms设置的和-Xmx一样大,理由:-XX:MaxMetaspaceSize :最大元空间。默认值比较大,万一元空间内存泄漏,会原创 2024-04-10 14:01:01 · 959 阅读 · 0 评论 -
【开发篇】十二、GCeasy报告分析
但发现这里JVM给元空间分配了1.2G,占用了652M,似乎没有到元空间阈值,这是JVM会动态的去计算元空间的阈值,只要元空间大小超过了这个阈值,就会触发Full GC,而这里的652可能已经多次超过了这个动态阈值。大量的对象在内存中创建,同时JVM不停的Full GC在释放内存,可以看到内存有一定的下降(如下图中我黄色圈圈起来的白色三角),但很快就被新创建的对象又占满了。从对象数据中也可以得到验证:每秒能产生198.76M大小的对象,晋升到老年代的也有65.85M数据,可见对象创建的确实很快。原创 2024-03-29 08:30:00 · 836 阅读 · 0 评论 -
【开发篇】十一、GC调优的分析工具
GC调优无标准答案,视不同的服务器配置、硬件、服务器可用资源等的影响。原创 2024-03-28 09:00:00 · 1342 阅读 · 0 评论 -
【开发篇】十、Arthas和BTrace在线定位问题
内存泄漏:不再使用一个对象了,但其在GC Root引用链上,不会被回收器回收,白占空间内存溢出:内存的使用量超出了JVM分配的上限,OutOfMemory持续的内存泄漏,导致可用空间越来越少。最后来个请求,彻底压垮并发请求,正常Java服务返回数据后,相关对象在内存中被释放,但当并发请求下,或者一次请求很大的数据量下,处理数据的时间变量,半天没返回,导致大量数据对象在内存中,进而OOM。原创 2024-03-28 08:30:00 · 906 阅读 · 0 评论 -
【开发篇】九、系统不处理业务时也占用大量内存
以上对tomcat的线程配置做了定制化,即最大线程500个,100个核心线程数,这100个线程,即使空闲下来,也不会去做回收。因此上面50个线程过来,最后会有500m的空间一直被占着。web请求过来,tomcat中的线程池等长时间不用了,线程被回收,ThreadLocal也就被回收了,按理说不会有内存泄漏。改成0,这样,内存一段时间后也会被回收。但改成0,这个配置不会生效,可以取一个最小值(10)。有一微服务,当系统没有用户在使用时,内存占用率也很高,导致实际可用内存大大减小。当然,你也可以将配置里的。原创 2024-03-27 09:00:00 · 917 阅读 · 0 评论 -
【开发篇】八、导出大文件导致内存溢出
推荐使用easyExcel,它采用分批导出,不会让内存中的对象数量达到一个很高的值。虽然这样会让导出的时间长一点,但不会OOM,分批,写完一批就从堆中清掉一批的对象。整体架构如下:客户端通过负载均衡达到Service,底下一堆Pod在干活儿,pod中的容器里运行着微服务。最后,文件的导出,选择导出到阿里云的oss。有个管理系统,支持几十万条数据导出到excel,但当有几十人同时导出文件,就会内存溢出。easyexcel用到的一个excel对应实体类,当前测试只用一列,那就直接一个属性就行。原创 2024-03-27 08:30:00 · 841 阅读 · 0 评论 -
【开发篇】七、mybatis的foreach遍历,SQL拼接导致内存溢出
打开直方图,发现线程对象占用排第一。打开支配树,按深堆排序,选占用最大的线程对象,找到处理器方法HandlerMethod,List objects --> with outgoing references查看其关联的对象。文章微服务升级,新增了一个传入文章id的List,判断有多少id是存在的接口,第二天高峰期内存溢出。在description中方找到当前线程在执行哪个方法。Visual发现size大时,JVM堆内存溢出。在本地启动Jmeter,调整size的值。原创 2024-03-26 14:03:01 · 512 阅读 · 0 评论 -
【开发篇】六、查询大量数据导致内存溢出
记录一个问题,工作中有个数据处理服务OOM,查了下镜像的dockerfile,发现JVM参数如下。很明显,一个数据服务,里面经手大量的数据对象,堆内存125的设置肯定不合理,调大至512m解决。继续看文章微服务的内存问题。原创 2024-03-26 10:50:08 · 993 阅读 · 0 评论 -
【开发篇】五、文章内容审核接口的内存问题优化
项目中要异步处理业务,或者实现生产者 – 消费者的模型,如果在Java代码中实现,那生产消费的速度、网络、远程调用的响应时间等影响,很有可能导致这些中间数据挤压,保存它们的同时占用了大量JVM堆内存,导致OOM,可使用MQ来实现。原创 2024-01-18 21:25:24 · 1138 阅读 · 0 评论 -
【开发篇】四、MAT堆内存分析(Memory Analyzer Tool)
MAT在打开当前的堆内存快照时,需要把快照下的堆内存里的所有对象读入到内存中,这对安装MAT的机器配置有要求,一般的开发桌面打不开这么大的快照文件,而且下载一个几十G的hprof文件,下行带宽小也头疼。MAT打开快照,点击查看对象直方图,里面也显示了一个对象的浅堆和深堆大小,点击表头可排序,执行脚本,启动MAT,分析堆内存,后生成报告,只需下载包告后解压,查看生成的这几个html报告查看即可。再看对象D,引用图中想指向对象D,从近处看,可以是对象B,也可以对象C,所以对象B、C对D。原创 2024-01-15 21:24:59 · 1662 阅读 · 0 评论 -
【开发篇】三、并发下的OOM分析
但并发时,如果数据处理时间很长,大量对象存于内存,或者处理用户请求后,没有及时删除用户数据对象,就会导致无用对象在堆内存堆积,进而OOM。用户请求过来, 后端查询数据库后封装Vo对象返回给前端后,然后正常这个Vo就可以被GC清理掉了。压测上面的接口,并发下OOM,则系统在用户高峰期一定会崩。调整下堆内存大小,方便后面验证。原创 2024-01-15 18:07:18 · 524 阅读 · 0 评论 -
【开发篇】二、代码中导致内存泄漏的错误写法
内存泄漏 --> 压测或者时间积累 --> OOM。原创 2024-01-12 18:05:23 · 983 阅读 · 0 评论 -
【开发篇】一、内存泄漏的分析工具
最后,可以使用Visual VM的采样tab页,查看当前堆里的对象信息。一个对象不再使用后,(因其从GC Root仍有引用链可达)却未被JVM回收,白白占着内存,即内存泄漏,这个效果累积,最后就会OOM。再引入另一依赖:micrometer,将JVM、数据库连接池的信息、磁盘信息等收集上来,组装成Prometheus能识别的格式。查看堆和元空间的走向:这里有size向max扩容的知识点,别看到高的线就认为空间不足。还有,如果是集群化部署,不是上面的java -jar一个jar包,远程就很不方便。原创 2024-01-12 10:36:20 · 2052 阅读 · 0 评论 -
【基础篇】十五、JVM垃圾回收器
最后,因为清理是复制算法,如果清理时发现没有空Region去存放转移的对象(没地儿复制了),则转为单线程执行标记-整理算法进行Full GC,此时会导致用户线程的暂停。指定使用PS+PO组合,看下他们的默认配置:发现默认吞吐为99,即用户线程执行99%的时间,执行GC时间占1%,最大暂停时间则很大,相当于没有设置。⚫ 这个选择一部分区域的实现思路是:G1下的Young GC,会记录回收每个区Region(Eden和幸存者区)的平均耗时,做为下次回收的依据,G1前,堆内存的划分是连续的。原创 2024-01-08 09:09:43 · 1029 阅读 · 0 评论 -
【基础篇】十四、GC算法
GC是在一个单独的线程,但不管JVM用哪种算法,都会存在一个阶段需要停止所有的用户线程,称Stop The World(STW),SWT大,用户用起来自然卡。补充:如果现在新生代已经满了,Minor GC还是满,再来对象,尽管新生代有的对象没到达年龄阈值,也会被搬到老年代。组合使用了上面的几种算法,被主流使用。以上三个指标,不可兼得。一句话:将存活的对象搬运到另一块空间,清理掉当前空间,互换名字。GC开始,把GC Root对象和可达的对象搬到To空间。清掉From空间,并把原来的To改为From空间。原创 2024-01-05 11:09:53 · 1116 阅读 · 0 评论 -
【基础篇】十三、强软弱虚引用、终结器引用
还可以根据引用的类型,不同的引用类型,对应对象的不同GC回收规则。原创 2024-01-05 11:08:32 · 1138 阅读 · 0 评论 -
【基础篇】十二、对象可回收的判断:引用计数法 & 可达性分析算法
线程不共享的部分,随着线程的创建而创建,随着线程的死亡而销毁,不会发生内存泄漏。即为每个对象维护一个计数器,对象被引用就+1,置为null了就-1,JVM扫描堆内存,发现数值为0则回收。上的内存进行回收,简化了对象的释放,但同时也丧失了回收的及时性,因为回收操作不再又开发者做了。Demo代码如下,循环体中创建的变量,一轮结束后自动没用,不用重复 o = null。普通对象A,经一个引用链可以到达GC Root对象,则A不可被回收。demo = null后,再无对Demo对象的引用,可回收。原创 2024-01-04 11:16:11 · 1232 阅读 · 0 评论 -
【基础篇】十一、JVM方法区
原因:JDK6下的intern方法,第一次遇到字符串实例时,复制到永久代的字符串常量池中,并返回常量池中的引用,即s1.intern是一个指向字符串常量池的引用,而s1后面是个对象,因此s1是指向堆的一个引用。,所以intern 方法会把第一次遇到的字符串的引用放入字符串常量池,此时,s1和s1.intern都指向堆里的think123对象,为true。如下,根据字节码,c指向字符串常量池,而a+b实际是用StringBuilder,得到一个String对象,指向堆内存,c不等于d。原创 2024-01-04 09:00:32 · 947 阅读 · 0 评论 -
【基础篇】十、JVM堆 && 直接内存
运行时数据区域,还有两组成部分:堆和方法区,和栈、程序计数器不同,它们是线程共享的。原创 2024-01-03 19:47:11 · 673 阅读 · 0 评论 -
【基础篇】九、程序计数器 && JVM栈
eg:看异常表的第一行,从起始PC 0到结束PC 2,如果发生异常,就跳转到第7行,astore_0即把捕获的异常对象e放到局部变量表中,因为catch块中大概率会用到e对象。可以看到,整个过程中,操作数栈里最多有两个数据,即操作数栈的最大深度为2,此时创建栈帧时,创建深度为2的操作数栈即可。实例方法中的序号为0的位置存放的是this,指的是当前调用方法的对象,运行时会在内存中存放实例对象。JVM的栈采用栈的数据结构,保存方法调用的基本数据,先进后出,其中,每一个调用的方法用一个叫。原创 2024-01-03 13:21:32 · 1277 阅读 · 0 评论 -
【基础篇】八、Arthas实现热部署
修改java文件的源码,这里容器对应镜像的基础镜像是OpenJDK,没有vi,我先退出阿尔萨斯,cp到hostPath再编辑。因为编译一个类可能需要其他import的类,此时可先查找下这个类的加载器的hash码:sc -d类全名。所以,用阿尔萨斯热更新,只是一种应急的手段,比如CICD线出问题了,平时还是正常编译、打包、部署。背景:修复了一个小bug,比如更改了一行代码,嫌弃重新打包部署太慢,想尽快看到效果。这种方式只是将字节码信息更新到了内存中,程序重启,字节码还是会恢复成就的。原创 2023-12-29 18:12:52 · 2452 阅读 · 0 评论 -
【基础篇】七、线程上下文类加载器打破双亲委派机制
SPI,Service Provider Interface,是JDK内置的一种服务提供发现机制。大致流程:写个小Demo直观感受下SPI,定义对外接口:其他类写实现类(这里接口和实现类放同一个jar中了,一般来说写接口和写实现的是两种角色):写文件:测试模块中引入上面的这个jar:尝试加载:成功获取到接口的两个实现类:JDBC使用DriverManager类来完成不同数据库厂商的驱动注册:DriverManager位于JDK的rt.jar包,由启动类加载器去加载,而引入的第三方jar包归应用原创 2023-12-29 17:06:41 · 931 阅读 · 0 评论 -
【基础篇】六、自定义类加载器打破双亲委派机制
*** 打破双亲委派机制 - 自定义类加载器try {try {System . out . println("自定义类加载器加载失败,错误原因:" + e . getMessage());} }/*** 打破双亲委派机制 - 自定义类加载器try {try {System . out . println("自定义类加载器加载失败,错误原因:" + e . getMessage());} }/**原创 2023-12-27 18:41:01 · 1498 阅读 · 1 评论 -
【基础篇】五、类的双亲委派机制
该机制下,向上查找、向下加载,如果有两个全类名相同的类,但内容不同,就只会有一个被加载。应用程序加载器的parent属性为扩展类加载器,而扩展类加载器的parent为null,这是由于Java代码中没法拿到启动类加载器的对象,因此赋值为null。双亲委派机制里的父加载器,这个父,不是Java继承的那个父,只是类加载器对象中有个成员变量叫parent,是上级关系,不是有继承关系。若全都没有被加载过,则从启动类加载器开始加载,当要加载的类不在启动类加载器的加载范围时,往下走到扩展类加载器,以此类推。原创 2023-12-27 18:33:12 · 1029 阅读 · 0 评论 -
【基础篇】四、类加载器ClassLoader
以上的应用场景是:需要开发一些偏底层的类时,并需要用启动类加载器去加载它时,可考虑添加这个JVM参数,并把jar包拷贝到对应目录。应用程序类加载器加载的是classPath下的类文件,包括项目中自己编写的类和接口的字节码文件,以及引入的第三方jar包中的类和接口的字节码文件。可以发现,JDK原来自己的ext目录下的类和我新加的jar包下的类,其类加载器都是扩展类加载器。扩展类加载器负责加载那些通用,但不是很重要的类:Java安装目录下/jre/lib/ext下的类文件。指令查看某个类的详细信息。原创 2023-12-26 21:04:57 · 970 阅读 · 0 评论 -
【基础篇】三、类的生命周期
分析:Test1类中有main方法,其执行,首先是Test1类被加载,初始化阶段静态代码块执行,输出D,往下执行输出A,再new Test1的对象,从字节码可以看到,实例代码块被放到了init方法字节码指令里,因此,最终结果:DACBCB。这个方法区只是一个概念层的东西,不同的JVM,甚至不同版本的HotSpot,设计方法区时都用到了不同的内存空间,比如早期的HotSpot,用的是永久带,而新版本中用的是元空间。这个类的Class对象,用于Java开发时获取类的信息,以及存储静态字段的数据。原创 2023-12-26 16:28:04 · 897 阅读 · 0 评论 -
【基础篇】二、字节码文件的组成 && Arthas + jclasslib +javap
关于jad命令一个应用场景:某天修复了一个bug后,换了新包,却发现bug还在,此时当然可以考虑重新部署一次,但这里就可使用jad来精确查看,现在部署的包里,bug所在类的源代码长啥样,从而明确知道是不是换包换串了。对应的软件打开文件时,对文件类型的判断不是依靠末尾的文件扩展名,而是依靠文件的头几个字节(文件头)来判断类型,如果该软件不支持这种类型,则报错。同理,str2中也通过符号引用指向常量池的#7,如此,只需在常量池中存一份,而别处去引用它即可,字节码文件变小,节省空间。原创 2023-12-25 21:13:51 · 1001 阅读 · 0 评论 -
【基础篇】一、认识JVM
JVM的启动时通过引导类加载器bootstrap class loader创建一个初始类initial class来完成的,不同的虚拟机,这个类也不同。JVM,即Java虚拟机,一台处理Java字节码文件(解释为二进制文件)的虚拟计算机,Java程序运行在Java虚拟机内部。开始执行Java程序时,JVM开始运行,程序执行结束,JVM也就停止了(jsp看JVM进程,会发现其随着程序的结束而结束)PS:Java虚拟机不关心运行在其内部的程序是用的什么语言,只要是遵循其规范编译的字节码文件,就都能运行。原创 2023-12-21 21:10:18 · 1573 阅读 · 0 评论