JVM内存区域的划分和介绍
JVM内存区域的划分和介绍
1、程序计数器(寄存器)
较小的存储空间,可以看成是当前线程执行的字节码的行号指示器;
字节码解释器的作用:通过改变这个计时器的值来选取下一条执行的字节码指令;
每个线程都一个独立的寄存器,各个寄存器之间互不影响,独立存储,我们称这类内存区域为线程私有的内存;
Native方法计数器为空,此内存区域是唯一一个在java虚拟机规范中没有规定任何outofmemoryerror情况的区域;
2、虚拟机栈
线程私有,生命周期与线程相同;
虚拟机栈描述的是java方法执行的内存模型
(每个方法在执行的同时会创建一个栈帧存储 局部变量、操作数栈、动态链表、方法出口等信息;每个方法从调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程)
3、本地方法栈
虚拟机使用到的native 方法
4、堆
所有线程共享,很大,虚拟机启动时创建;
new 出来的东西都在这,数组,对象实例;
5、方法区
线程共享,存储被虚拟机加载的类信息(类的名称、方法信息、字段信息),常量,静态变量,编译器编译后的代码;
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。
运行时常量池
在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
是连续的内存空间,通过
-XX:MaxPermSize
可以设定方法区(永久代)最大分配空间。JDK1.8以前方法区的缺点:
1、为方法区分配多大的空间很难确定,JVM开始加载的时候不知道加载多少个类,常量池的大小,方法的大小都是不可控的。
2、动态加载类的情况越来越多,这块内存越来越不可控
3、设置小了,当JVM加载类的信息容量超过这个值,系统运行过程中很容易出现OOM:PermGen错误,设置大了又浪费了内存。
JDK1.8以后对虚拟机进行了改进
移除了方法区,新增了元空间的概念。
元空间(Metaspace)是放在JVM内存之外的直接内存里面的。
元空间是存储类的相关信息。
元空间的参数:MetaspaceSize
MaxMetaspaceSize
将常量池和静态变量放到了堆中。
直接内存
直接受操作系统管理,而不是虚拟机。
可以在一定程序上减少GC对应用程序的影响。
堆内存中,进行的分代管理
新生代
新生代 : 老年代 = 1 : 2
新生代包括 Eden : S0 :S1 = 8:1:1
1、大部分情况,对象优先分配在Eden区,如果对象太大了,新生代都放不下,会直接放到老年代。
2、当Eden区满了后,JVM会触发一次Minor GC
Minor GC
如果对象经过了15次的Mionr GC后,还存活,那就进入老年代。可以通过设置
MaxTenuring Threshold
指定。进入老年代不是只有大于
MaxTenuring Threshold
才可以。如下也可以概括:对象总和的占比大于空间的一半。
老年代
Full GC
1、老年代的对象较稳定,Full GC 不会频繁的执行,Full GC 会清理整个堆空间。
2、在进行Full GC 之前,一般都进行了Minor GC, 使得新生代的对象晋升老年代,导致空间不足才触发
3、无法找到足够大的连续空间分配给新创建的比较大的对象时,也会提前触发一次Full GC
4、Full GC 会导致Stop The World ,阻塞其他线程。
堆外内存(直接内存)
为什么要分出堆外内存?
因为JVM进行Full GC 时,势必是对堆内的所有对象进行扫描。如果可以将堆内存划小一点,那Full GC的效率又会高一点。
1、减少了垃圾回收
使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
2、提升复制速度(io效率)
堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。
堆外内存泄露
1、堆外内存不会向新生代和老年代那样,发现空间不足就通知收集器进行回收。
2、它只有等Full GC 时,JVM才会“顺便地”清理堆外内存中废弃的对象。
3、极端情况下,JVM没有执行Full GC ,堆外内存也一直得不到释放。
JVM可能会发生哪几种OOM?
造成OOM的原因
1、请求创建有一个超大对象,通常是一个大数组
2、超出预期的访问量、数据量,比如流量飙升的促销,秒杀活动
3、内存泄露的出现,大量对象引用没有释放,JVM无法对其自动回收,如ThreadLocal、线程池的无界队列。
解决方案:
一般情况下通过-Xmx参数调高堆内存空间即可,如果没有解决,就考虑一下我们的代码问题。
补充:
一般为我们的JVM进程分配2G的内存,这2G的内存再分为堆内内存,堆外内存。