文章目录
前言
java 和 cpp 有个很大的不同就是 java 程序在运行时候不需要程序员编写代码来处理内存的回收释放,java 在 jvm 的基础上增加了垃圾回收 gc 机制,会在合适的时间段触发垃回收器,这提高了代码的安全性,也使得 java 编程更容易了
java 内存概况-
栈内存(线程私有)
栈内存数据共享,但线程中是私有的。代码运行时候,每调用一个方法都会在栈内存创建一个栈内存块(栈帧),栈帧由局部变量,操作数,帧数据构成。栈内存满足 FILO 的原则。当方法结束后(栈内存块结束的时候),JVM 会自动释放分配空间(栈帧会被自动释放),栈内存并不是由 gc 所释放的,它是由编译器来释放的。栈内存的存取速度快于堆内存仅次于寄存器。由于栈的大小也是有限的,如果方法调用过多,也就是栈帧中有栈帧,不断重复下去,就有可能导致栈溢出
-
堆内存(线程公有)
堆内存中三分之一为新生代,,三分之二为老年代,堆内数据非共享,但线程共享。一般操作系统自己会维护一个记录空闲内存空间地址的链表,当系统收到程序需要申请地址的时候,程序会遍历整个链表,寻找第一个大于申请空间的结点,然后将该结点从链表中删除,因为空间大于申请的空间,所以剩余的空间会被存放到空闲空间结点链表中,对于大多数操作系统,这个申请到的内存空间首地址会记录分配空间的大小,方便 java 的 gc 快速回收。新生代存在小 gc,老年代存在大 gc
- 栈内存
- 虚拟机栈(基本数据类型,对象的引用类型,方法的调用即栈帧)
- 本地方法栈(native 方法的调用就是用 java 调用其他编程语言的实现)
- 堆内存
- 新生代(占据 1/3 堆空间,小 gc 的作用地方)
- 伊甸园
- 从生存者
- 至生存者
- 老年代(占据 2/3 堆空间)
- 方法区(class,final,static)
- 程序计数器
完整 gc 回收的过程
-
方法区存放类信息
类加载器在类的加载的时候,从类文件中提取类信息存放进方法区中,这个类信息与 class 对象可不是一个概念,类信息存放在方法区,class 对象存放在堆内存,方法区的类信息更像是堆内存 class 对象的一个接口,通过这个接口来产生特定的对象
-
程序申请内存空间
与 cpp 这样的语言不同的是,由于 java 的特性,其能够比较方便开辟和释放内存空间,申请内存空间的时候,会首先遍历操作系统维护的一个空闲地址的链表,从头往后遍历,一旦遍历到第一个空间比所申请空间大的结点,就把此节点从链表中剔除,然后将多余的空间存放到该链表中,拿到的这一块空闲空间,它的首地址会记录本次分配空间的大小,这样方便 gc 快速回收
-
伊甸园中产生新的对象
在程序申请到内存空间之后,通过方法区的类信息来生成 class 对象,该对象首次创建会存放在伊甸园中,伊甸园占新生代的 80% 的空间,新生代又占据堆内存 1/3 的空间
-
伊甸园的对象首次存满了之后触发小 gc(YGC)
由于 jvm 虚拟机中会存在一条引用链,这条链表根结点是 gc root,结点主要包含下面几种:对象的引用,方法区的常量和静态变量,在小 gc 会做的事情是遍历这条引用链,当发现有一个对象没有关联到其中一个引用链结点的时候,这个对象的空间就会被释放掉,伊甸园中存活的对象会移动到 s0 区
-
伊甸园的对象再次存满之后触发小 gc(YGC)
伊甸园再次存满,通过小 gc 遍历引用链,同样发现伊甸园中若有对象没有关联到任何一个引用链结点的时候,这块空间就可以得到释放,然后把伊甸园中剩余的存活的对象数据移动到 s1 区,s0 区也采用类似伊甸园中的操作把剩余存活的对象数据移动到 s1 区。以后每次经历小 gc 都有两个过程:伊甸园存活对象移动到 s0(s1),然后 s1(s0)存活的对象也移动到 s0(s1)
-
新生代超过 15 小 gc 的对象转移到老年代
如果在新生代的对象经历了达到 15 次小 gc 则被 jvm 判定为此对象可能后续也难以被释放空间,所以下次小 gc 的时候该对象会被转移到老年代中而非 s0 或者 s1 中
-
老年代占满触发大 gc(full gc)
随着时间的推移,空间较大的老年代也会被对象占满,占满后会触发大 gc,大 gc 也要寻根判断,释放掉没有关联到引用的对象空间,大 gc 中实际也会伴随了小 gc,所以叫 full gc,但是要注意的是如果经历了很多次大 gc 后发现老年代无法再释放掉多余空间,这时候就会发生内存溢出的现象。jvm 性能调优的一个重要因素就是延缓大 gc 的间隔时间。对于小 gc 而言使用的标记-复制算法,而对于更不频繁的大 gc 使用的标记-清理和标记-整理算法
jvm 启动参数
-Xms
来定义堆内存的启动大小,-Xmx
来定义堆内存的最大大小
排查引起内存溢出的原因
-
检查代码中是否有大量循环,大量循环的代码中如果创建对象很容易导致内存溢出;
-
检查对数据库的查询中,若果一次性取太多数据记录到内存也会导致内存溢出,建议对数据库查询采用分页查询