【JVM|内存结构】第一天

发布于:2025-06-24 ⋅ 阅读:(14) ⋅ 点赞:(0)

摘要:

        本文主要介绍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释放直接内存(由后台线程执行,而非主线程)


网站公告

今日签到

点亮在社区的每一天
去签到