摘要:
本文主要介绍JVM的内存结构,包含程序计数器、虚拟机栈、本地方法栈、堆、方法区和直接内存等核心组件。程序计数器记录指令执行地址;虚拟机栈存储方法调用信息;堆存放对象实例,涉及垃圾回收;方法区(元空间)存储类信息;直接内存用于高效I/O操作。JVM通过线程私有区和共享区的设计平衡性能与安全,同时提供内存溢出检测和调优机制。StringTable作为字符串常量池优化内存使用,而直接内存则通过Unsafe类管理,提升I/O性能。理解JVM内存结构有助于诊断性能问题和优化Java应用。
一,概念
1,什么是jvm?
jvm是Java二进制字节码的运行环境
2,jvm有什么好处
(1)一次编写,到处运行,跨平台
(2)自动的内存管理,垃圾回收功能
(3)数组下标越界检查,抛出异常
3,jvm,jre和jdk之间的关系
二,jvm内存结构
1,程序计数器(物理上采用寄存器实现)
记录下一条jvm指令(由Java代码编译的二进制字节码构成)的执行地址,是对物理上的寄存器的抽象实现。
程序执行顺序:Java代码-->二进制字节码(jvm指令)-->解释器-->机器码-->cpu
(1)线程私有的,每个不同的线程都有自己的程序计数器
(2)不会存在内存溢出
2,虚拟机栈
每个线程运行时所需要的总内存,称为虚拟机栈,每个栈对应多个栈帧,每一个栈帧都是一次方法调用,栈帧的内存大小由方法的变量/局部变量决定,方法结束栈帧就会被释放,每个线程运行时都会有一个初始的活动栈帧。
(1)方法内的局部变量是否线程安全?
如果方法内的局部变量没有逃离方法的作用访问,那么就是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,就要考虑线程安全,如:方法形参传递等
(2)内存溢出
栈帧过多导致内存溢出,例如:方法递归
栈帧过大,直接将整个栈占满(少见)
(3)线程诊断
top定位哪个进程对cpu占用过高。
ps H -eo pid,tid,%cpu(进程id,线程id,cpu占用率)| grep 进程id,进一步确定哪个线程对cpu占用过高。
jstack 进程id命令,根据线程id(16进制)进一步定位其中有问题的线程,甚至于问题代码。
3,本地方法栈(native method stacks)
为本地的一些方法调用提供内存空间,这些方法大多数都是与操作系统交互的方法,底层由C/C++实现,需要单独分配空间。
4,堆(Heap)
通过new关键字创建的对象和数组都会存入堆中,它是线程共享的,需要考虑线程安全问题,同时它还有垃圾回收机制,用于清理废弃的对象
堆内存溢出(java.lang.OutOfMemoryError:Heap)
- 内存泄漏:程序不正确地持续持有对象引用,导致垃圾回收无法回收这些对象,从而堆逐渐耗尽。
- 对象太大:请求分配的对象或数组太大,超出了堆的最大容量。
- 堆设置过小:JVM运行参数中堆的大小设置太小,不足以满足程序需求。
5,方法区(1.8以后)
将原来jvm内存结构中的属于方法区的类信息、类加载器和常量池整合到元空间,放入到操作系统的本地内存中,其大小和物理的内存相关
元空间内存溢出(java.lang.OutOfMemoryError:MetaSpace)
过多的类进行加载可能导致元空间内存溢出,例如:spring和mybaties等,都会动态地加载繁多的动态代理类,就有可能导致内存溢出,但是自1.8更改到元空间后,其内存因为直接与系统内存大小相关,极少出现元空间内存溢出。
6,常量池
本质就是给jvm指令提供一些常量符号引用的表,通过这些引用在常量池中找到要执行的类名、方法名、参数类型、字面量等信息
例如:根据#2查表,就会得到java.lang下的System.out,out的类型java.io.PrintStream
7,StringTable(HashTable结构,值唯一且不能扩容)
StringTable 是一个哈希表或者类似的存储结构,用于缓存和存储所有在Class文件中或运行时创建的字符串对象。
例如:String s = "a",当a符号变为Java字符串对象时,先在StringTable中查找,没有相同的就会将“a”对象加入StringTable["a","b".....] 中
(1)Stringtable添加规则(与堆中的对象要分清)
(2)垃圾回收
当StringTable因为分配过多对象导致空间不足时会触发垃圾回收,根据垃圾回收算法将“无用的”对象回收
(3)调优
如果数据较多,增大-XX:StringTableSize=...值,避免Hash碰撞增加查找速度,提升性能
通过instern()使字符串入串池,减少重复字符串的个数对内存的占用
三,直接内存(直接使用unsafe进行回收)
1,常见于NIO操作时,用于数据缓冲区
2,分配回收成本高,但是读写性能也非常高
3,不受JVM内存回收管理(底层由unsafe对象进行获取和释放)
补充;回收过程简单来说就是byteBuffer对象被垃圾回收后,根据虚引用的垃圾回收机制,会自动触发Cleaner虚引用对象的Cleaner方法,这个方法会执行对应的任务对象-->unsafe释放直接内存(由后台线程执行,而非主线程)