1). JVM整体结构及内存模型
注意:
以上图中每个线程都会有自己的栈、本地方法栈、程序计算器的,它们的数据是独立不共享的。
而堆、方法区则是共享的,不同的线程都可以使用。
1.堆:(先进先出)
1.1:堆主要存放的东西:
1.1.1:对象,如Math对象math、用户对象user等。如上图,栈帧compute()里的局部变量math实则存放的是堆里的math对象的内存所在地址,也就是说栈帧里的math是直接引用堆里的math的。
1.2:堆内存:
1.2.1:年轻代:
1.2.1.1:Eden(默认是占用10分之8),一般对象都是放在Eden中
1.2.1.2:s0(默认是占用10分之1)
1.2.1.3:s1(默认是占用10分之1)
1.2.2:老年代:
1.2.2.1:默认是占用3分之2,从Eden开始经过15次gc还没有释放掉的数据最终会放到老年代中。
1.2.2.2:一般程序的静态变量、对象池、缓存、Spring容器中的对象都是很容易放到老年代中的。
2.栈:(先进后出)
2.1:栈主要存放的东西:
2.1.1:局部变量,比如main()方法中的局部变量math、compute()方法的a\b\c;
2.1.2:操作数栈,主要功能是将程序即将要执行代码的JVM指令值压入操作数栈中,并做对应的操作(如加、减、乘、除等JVM指令操作),如程序执行a+b的时候会把a和b的值压入操作数栈并执行相加的JVM指令。操作数栈可以理解为程序在运行过程当中一些操作(如加、减、乘、除等JVM指令操作)的临时中转存放空间。
2.1.3:动态链接,程序的静态链接在程序启动类加载完成的,动态链接则是在程序运行的过程中记录一些符号引用的所在内存位置,比如程序在执行math.compute()的时候会在动态链接里记录compute()方法代码的内存位置。
2.1.4:方法出口,比如程序在执行完compute()的时候要进行返回了,方法出口就是记录要返回到main方法的那一个位置上。
2.2.栈(FILO)是先进后出,如下面的Math类代码,先执行Math math = new Math();会在栈底先加入main栈帧,再执行math.compute的时候会先在栈里压入compute()栈帧,执行完计算方法后compute()栈帧先出栈(销毁)最后才是main()栈帧出栈,这就是栈的先进后出,是非常符合程序运行逻辑的。
2.3.栈帧,如main函数在执行的时候系统会分配置一小块栈帧内存给main函数进行局部变量math的存放,当继续执行math.compute方法时系统也会在栈内存空间中再分配一小块独立的栈帧内存空间给方法compute进行存放局部变量a、b、c。
2.4.如果是方法的递归循环调用也是会在main函数栈里为每一个循环的方法分配一个新的栈帧内存空间的。
2.5.栈帧,为函数分配的栈帧内存空间,栈帧里的局部变量是互相独立的。
3.本地方法栈:现在用的不多了,主要是用于native方法时运行所需的内存空间,如:
native主要是用于跨语言调用底层c、c++的一些方法。
4.方法区(元空间):主要存放的东西:
4.1.常量(final)
4.2.静态变量(static),如下图的静态变量user,它是存放在堆里的,而方法区里存放的user实则是堆里的user的内存地址,也就是说方法区里的user是直接引用堆里的user的。
4.3.类信息(如程序中Math类所对应在C、C++底层的类结构信息)
5.程序计数器:每个线程都会有自己的独立程序计数器,存储程序即将要执行的代码的位置,方便于多线程线程挂起后再重新执行的时候程序知道要从那行代码继续执行。
2).通过下图一个简单的代码来详了解JVM内存中的运行处理
1.程序在执行static void main:
1.1先为main线程分配一块main栈内存空间
1.2再为main函数在main栈内存里分配一块main()栈帧内存
2.程序在执行Math math = new Math();
2.1在堆里存放一个对象math
3.执行math.compute();
3.1为compute函数在main栈内存里分配一块compute()栈帧内存
3.2执行compute()里的int a=1;
3.2.1在compute()栈帧内存的局部变量表中定义a
3.2.2在compute()栈帧内存的操作数栈中压入数值1
3.2.3在compute()栈帧内存的操作数栈中取出数值1给局部变量表中的a
3.3执行compute()里的int b=2;
执行过程和3.2的一样
3.4执行compute()里的int c = (a+b) * 10;
3.4.1在compute()栈帧内存的局部变量表中定义c
3.4.2在compute()栈帧内存的操作数栈中压入a的值1
3.4.3在compute()栈帧内存的操作数栈中压入a的值2
3.4.4在compute()栈帧内存的操作数栈中取出1和2
3.4.5将1+2的JVM加法指令结果3压入操作数栈中
3.4.6在compute()栈帧内存的操作数栈中压入10
3.4.6在compute()栈帧内存的操作数栈中取出3和10进行JVM乘法指令结果30
3.4.7将结果给compute()栈帧内存的局部变量C
3.5执行return c;
3.5.1根据方法出口返回到main方法即将执行的代码位置上