JMM详解


title: JMM详解

JMM提纲

JMM组成

问到JMM一般的认知情况下会出现下面两种情况
1.没错内存模型就是堆栈
2.知道堆栈 方法区 计数器
由上可以知道堆栈其实比较重要也比较深入人心,下面继续说内存模型
按大方向可以分为两种
1.堆
2.非堆
看下面一张图
JMM详解
再细分一下就是下面五种
1.栈
2.本地方法栈
3.方法区
4.计数器
5.堆
前面四种属于非堆内存,明白这个挺重要。咱们在设置JVM的最大内存Xmx的时候其实设置的是堆内存,32位的操作系统进程的内存大小是有限制的
这里就会出现一个坑
因为堆太大,但是进程内存就那么一点。所以可想而知能分给栈和方法区的内存就没多少会导致OOM。
64位的操作系统没有这个问题

栈是LIFO的一个数据结构模型用来存储局部变量表 操作数栈 动态链接 方法出口等信息
并且是和线程的生命周期相同,线程独享
咱们看下面一段代码的执行过程就能很清晰的知道栈的过程。
分析这种抽象的东西还是得靠代码和调试工具,才能摸着内存。

public class Memory {

	public static void main(String[] args) { // Line 1
		int i=1; // Line 2
		Object obj = new Object(); // Line 3
		Memory mem = new Memory(); // Line 4
		mem.foo(obj); // Line 5
	} // Line 9

	private void foo(Object param) { // Line 6
		String str = param.toString(); //// Line 7
		System.out.println(str);
	} // Line 8

}

JMM详解
咱们具体分析一下代码过程
1.当程序执行的时候执行main()方法压入栈底
2.创建局部变量i=1放入main方法的方法栈帧中
3.在堆中新建一个Object对象,栈里面存放引用refrence
4.同第三部一样
5.新建一个foo()方法的栈帧
6.因为Java按值传递所以新建一个新的param引用
7.在堆上面新建一个字符串对象,并在栈上新建一个引用
8.foo()执行结束
9.main()执行结束,销毁堆栈。如果是线程复用,线程没销毁那么就是全部出栈结束。不会销毁堆栈~
再说说栈的oom这一块存在两种异常
1.*(一般递归容易出现导致栈深度超过虚拟机规定值,瞬间爆炸)
2.OutOfMemoryError(内存不够)
正常来说一般栈的大小默认为1M但是咱们一般设置为256k。因为过大浪费内存空间,导致没法新建更多线程
而且一般虚拟机都存在栈动态扩展功能,有两种实现方式
1.双向链表 segmented stack
2.拷贝复制 stack copy
好了栈基本就没啥可以说的了

线程独享,唯一一个没有OOM的地方,至于为什么上一篇解释过。
计数器主要用来记住当前线程执行的指令位置

线程共享
java堆基本所有的对象实例都存放在这个里面,也是垃圾回收器管理的主要区域。
分为四个区域
1.Eden
2.from survivor
3.to survivor
4.old
前三个是新生代第四个为老年代
再细分一下就是TLAB每个线程占用一小块区域,为了更方便垃圾回收以及更快分配内存。这块后面再说
咱们还是看一张图
JMM详解
咱们可以通过Xmx和Xms设置最大和起始堆内存大小,一般设置为一样的减少不必要的垃圾回收和性能消耗
但是这个的前提是对于程序要使用到多大的内存有一个深入的了解,否则就会失去内存扩展性也浪费资源。

线程共享
用来存储类信息 常量 静态变量 即时编译器编译后的代码
看到上面存储的应该就可以想到了热部署什么的为什么会经常OOM了吧
还有项目启动的时候报一个OOM什么的这都是常规操作,闭着眼睛都知道哪里出问题了。
在方法区中还有一个常量池
说白了就是存放常量的比如说字符串呀,例如String 的 intern()方法。

最后了还有一个直接内存这么一个东西并不属于JMM的一部分,而是通过native方法直接获取外部内存
所以考虑内存的时候要把它也算进去,否则会导致OOM的莫名其妙出现。

欢迎扫码加入知识星球继续讨论
JMM详解