java之运行时内存

1.概念:

       一个java程序从编译到执行,大概经历过4个步骤,编译成字节码,用类的加载器进行加载,分配内存,引擎执行,而内存的如何分配,是我们平时对程序进行调优的主要对象。

2.有几个区域:

       大体来说分为2个,线程共享和线程独享,线程共享又分为方法区和堆,线程独享分为程序计算器,栈,方法栈。

3.逐一介绍:

       1.栈:

       众所周知,栈是一个先进后出的数据结构,在java中是用来存储局部变量的,你有想过为什么这个区叫栈,而不叫队列,不叫链表呢?据我所了解,栈里面存储着叫做栈帧的东西,这个东西又分别由4个东西组成,局部变量表,操作数栈,方法出口,动态链接,大概是这样:

java之运行时内存

 然后我们来执行一段代码:

java之运行时内存

然后我们通过javap来反编译成我们看的懂的字节码:

java之运行时内存

            我们用赋值语句int a = 1;来说一下,就这一句代码在字节码表现为0:iconst_1;1:istore_1,这是什么意思呢?第一句的意思是我们把一个大小为1的数押进操作数栈,第二句的意思是我们把操作数栈为1的值赋值给变量表中中的变量(就是a),由此我们可以认识到,操作数栈的作用其实是用来计算的,而局部变量表是用来存储局部变量的,然后当一个栈帧运行完后会通过方法出口传给下一个栈帧因为一个栈不只一个栈帧,就像这个class这样,在math()方法执行完后要返回到main()方法里面,具体这个栈帧是如何分配的我就不太清楚了。

2.计数器:

         从上面的例子我们可以看出,每一条反编译的字节码语句左边都有一个number,而这个number就是我们的计数器,计数器记录着我们的线程执行到哪一步,以便在栈帧中转换或者多线程切换时记录下一步。

3.本地方法区:

        什么是本地方法区呢,我们都知道java是c语言编写的,而本地方法区就是调用这些底层的.dll文件,例如Thread,你可以查看一个它的start()的实现,里面会有一个start0()的方法,而这个方法是被native所修饰的,这就是一个存放在本地方法区的方法。

4.方法区:

       方法区是存放静态变量,常量,类的字节码的地方(就像我们上面给的代码用javac编译一次所生产的机器码),这个东西以前叫做永久代,现在叫做元空间。

5.堆:

       这个是重点,很多公司面试都会问,因为我们通常调优都是在调这个东西,首先堆从大体来分分为2个区域,新生代和老年代,而新生代又分为伊甸园区和生存区,大概是这样;

java之运行时内存

     为什么这样分呢?因为2者用了不同的GC回收算法,一般我们new一个对象,这个对象的实体都会放进这个区域,而第一次一般都会放在Eden区,如果这个区满了以后会存放到Service区(也就是那2个小区的其中一个),如果都满了呢,就会进行一次GC,而新生代的GC算法就是复制算法,这个算法就是离用另外一个Service区把其他2个区所存活的对象,复制放进这个区里面,然后清空原来2个区,然后如此反复,把存活了n次的对象就会进入到老年区里面,如果老年区也满了呢,老年区就会进行一次老年区的GC标记-整理算法,如果还是满了呢?就会进行一次full GC(stop the world),这是很致命的,这会导致你整个java程序直接dang掉,而这一般也会发生在大型的并发项目里面,如果fullGC还不够,就会抛出java.lang.OutOfMemoryError: Java heap space ,所以我们一般都要进行调优来减少GC的次数,特别是fullGC。