JVM内存区域


Java内存区域

1. 进程与线程

进程 :进程是程序的一次执行过程。系统运行一个程序就是进程从创建到运行再到消亡的过程。

线程 :一个进程中包含多个线程,线程共享进程的堆和方法区的资源以及直接内存,同时线程私有资源的包括 程序计数器虚拟机栈本地方法栈

2. JVM运行时区域

2.1 程序计数器:
  • 字节码解释器通过改变程序计数器的值来一次读取指令, 实现代码的流程控制 :如顺序执行、选择、循环、异常处理等;
  • 多线程情况下,程序计数器用于 记录当前线程执行的位置,保证当线程切换回来时可以继续执行
  • 程序计数器私有化保证了线程切换回来后可以从正确的位置继续执行。
  • 唯一一个不会出现OutOfMemoryError的内存区域,生命周期随线程的创建而创建,随线程的结束而死亡。
2.2 Java虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型,生命周期与线程相同。每个Java方法在执行时会创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

虚拟机栈中会出现StackOverFlowError和OutOfMemoryError两种异常。

2.3 本地方法栈

和虚拟机栈类似,区别在于 虚拟机栈为执行Java方法服务,本地方法栈则为执行本地方法服务 。本地方法栈中也会出现StackOverFlowError和OutOfMemoryError两种异常。

2.4 堆

堆是JVM所管理的内存中最大的一块,是线程共享的一块内存区域,在JVM启动时创建, 用于存放对象实例,几乎所有的对象实例以及数组都在堆上分配内存

堆是垃圾收集管理的主要区域,也被称为GC堆。从GC的角度,当前的垃圾收集器都基本采用分代垃圾收集算法,因此堆可以细分为:新生代、老年代、永久代(JDK1.8被移除,被直接内存中的元空间代替)。

2.4.1 新生代

新生代用于存放新生的对象,一般占据堆空间的1/3。新生代又细分为Eden、From Survivor以及To Survivor,三者占比为8:1:1。新生代中会频繁地进行Minor GC操作实现垃圾回收。

  • Eden:新创建的对象绝大部分会先分配在Eden区,如果对象太大会直接分配到老年代。当Eden区域内存不够时,就会触发Minor GC(新生代采用复制算法),对新生代进行一次垃圾回收。
  • From Survivor和To Survivor:在GC开始时,对象只会存在于Eden和From Survivor区,To Survivor为空。在一次Minor GC发生后,仍然存活的对象会移动到To Survivor,其年龄加1,并清空Eden和From Survivor区。当对象的年龄达到一定程度(默认为15),就会进入到老年代。Minor GC完成后,From Survivor和To Survivor功能互换,下一次Minor GC时,会把To Survivor和Eden区存活对象放入From Survivor区中,并计算对象存活的年龄。
2.4.2 老年代

老年代主要存放应用中生命周期长的内存对象。老年代比较稳定,不会频繁地进行Full GC。在进行Full GC前会进行一次Minor GC,当新生对象进入老年代导致内存不够时Full GC才会触发。当无法找到足够大的连续空间分配给新创建的较大对象也会提前触发一次Full GC进行垃圾回收腾出空间。

老年代中Full GC采用标记-清除算法 ,Full GC的耗时比较长,因为要扫描再回收。Full GC会产生内存碎片,当老年代也没有内存分配给新来的对象的时候,就会抛出OutOfMemoryError异常。

2.4.3 永久代

永久代指的是永久保存区域,主要存放Class和Meta(元数据)的信息。在JDK1.8中,永久代被元空间代替,元空间不在虚拟机中,而是使用直接内存。

采用元空间不使用永久代的原因:

  • 为了解决永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代大小指定比较困难,大小容易出现永久代溢出,太大容易导致老年代溢出(堆内存不变,此消彼长)。
  • 永久代会为GC带来不必要的复杂度,并且回收效率偏低。
2.5 方法区

方法区与对一样都是各个线程共享的内存区域,用于 存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

JDK1.8中将方法区移除,取而代之的是元空间,位于直接内存中。

方法区是Java虚拟机规范中的定义,是一种规范。永久代是对方法区的一种实现,是HotSpot的概念。

2.6 运行时常量池

运行时常量池是方法区的一部分,用于 存放编译期生成的各种字面量和符号引用 ,收到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

JDK1.7及之后版本将运行时常量池从方法区中一处,在 中开辟一块区域存放运行时常量池。

2.7 直接内存

直接内存不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁使用,也可能导致抛出OutOfMemoryError异常。

JDK1.4中新加入的NIO(同步非阻塞IO),引入了一种基于通道(Channel)和缓存区(Buffer)的I/O方式,可以直接使用本地函数库分配对外内存,然后通过存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,避免了在Java堆和本地堆之间来回复制数据。


原文链接:https://www.cnblogs.com/chiaki/p/13472146.html