jvm 内存结构部分
jvm 内存结构部分
-
程序计数器【寄存器】:记住下一条jvm指令的执行地址程序;
- 线程私有的,每个线程都有自己的程序计数器;
- 不会存在内存溢出;
-
虚拟机栈:线程运行需要的内存栈,一个线程一个栈;
- 栈是由栈帧组成,通常在1MB以内;
- 一个栈帧就是每个方法运行时,需要的内存【参数,局部变量,返回地址】;
- 每个线程只能有一个活动栈帧,对应当前执行的方法;
- 活动栈帧在虚拟机栈的顶部;
- 栈内存会自动释放;
- 栈越大,会方便递归调用;
- 方法内的局部变量的作用域仅在栈帧内,没有返回,则线程安全,有返回且是对象,则不一定;
- 栈溢出:
- 递归调用,栈帧过多;
- 栈帧过大;
-
本地方法栈:java虚拟机调用本地方法【其他语言实现的反感发】提供内存空间;
-
堆:
- 线程共享的,需要考虑垃圾回收机制;
- 通过new 关键字创建的对象都会使用堆内存;
- 有垃圾回收机制;
- 内存诊断工具:jvirsualvm;
-
方法区:
-
逻辑上是堆的一部分,溢出时,会和堆抛出相同异常:OutOfMemoryError;
-
Metaspace【元空间,默认使用系统内存】
-
Class,类信息;
-
ClassLoader,类加载器;
-
常量池;
-
其中的字符串常量池在堆中;
-
常量池就是一张表,虚拟机根据这张表找到要执行的类名,方法名,参数类型,字面量等信息,这些全在.class 文件中;
-
运行时常量池,当类被加载时,它的常量池就会放入运行时常量池,并把里面的符号变为真实的地址;
-
串池【StringTable是一个hash表】;
- 当遇到没有在串池,才加入;
- 根据Java的规范,只有当直接使用字面量或者调用
intern()方法时,才会将字符串放入常量池; - 使用itern()方法,主动将字符串对象放入串池,且都会将池中对象返回:
- 如果有,则不会放入;
- 如没有,则放入,是直接放入,最后返回值和放入值是相同【1.6版本是复制放入,返回值和原值不同】;
//到StringTable中寻找,没有,创建; String s1 = "a"; String s2 = "b"; //字符串变量的拼接:创建StringBuilder()对象,然后new String(value),"ab"不会自动放入常量池中; String s3 = s1 + s2; //字符串常量的拼接:字符串是常量,s4是常量,所以编译期间做了优化; String s4 = "a" + "b" = "ab"; s4 = s3.intern();
-
-
-
StringTable
- 1.6【PermGen】和1.8【堆空间】版本不一样,所存在的空间不一样;
- :StringTable使用相当频繁,而且1.6是复制放入串对象,无法引用,而且垃圾回收效率低【触发回收不同】,所以跟新;
- StringTable的垃圾回收;
- 底层是一个hash表;hash + 链表;
- 存在大量重复的字符串,可以入池【intern()】,以减轻堆内存的空间;
- 1.6【PermGen】和1.8【堆空间】版本不一样,所存在的空间不一样;
-
-
直接内存:【系统内存】
- 常见于NIO操作,用于数据缓冲区;
- 分配回收成本较高,但是读写性能高;
- 不受jvm内存回收管理;
- 文件读取流程:
- java程序调用系统函数【内核态:System】读取磁盘文件;
- 系统内存会创建系统缓存区【java读取不到】,然后再读取到java堆内存的java缓冲区byte[],然后由java程序操作;
- 直接内存:java程序和系统都能够直接访问的内存区;
- 垃圾回收并不能回收直接内存空间,这里是通过Unsafe类来手动分配和释放的;
- 通过一个Cleanner类关联java对象ByteBuffer,当ByteBuffer被gc回收的时候,自动Cleanner类创建与一个线程,然后使用unsafe释放【Cleanner 等效 finalize()方法】;
- 显示调用 System.gc() => full GC;
- 暂停时间较长;
- 因此等待java自动垃圾回收,直接内存长时间得不到回收,因此直接手动回收,unsafe.freeMemory();

浙公网安备 33010602011771号