背景
-
要理解JVM的内存模型,应该提出一些什么问题?
-
问题:
a) 内存区域是否是线程共享的?b) 内存区域存储的是什么?
c) 内存区域相互之间大小比例关系?eden : from : to = 8 : 1 : 1
d) 内存区域相互之间的联系?比如对象从伊甸园到新生代到老年代的过程。
e) 内存区域是堆上的还是堆外的?
f) 永久代是堆上还是堆外?又应该如何理解?
-
本地内存可以理解为JDK NIO中的直接内存。堆上内存理解为JVM内存模型的一部分。而JDK1.7的永久代可以理解为JVM内存模型的一部分,它的目的是存储加载的类信息,常量,静态变量,但它不是在堆上的,是单独分配的一个连续的JVM内存空间。而JDK1.8的永久代其实是直接内存或者理解为本地内存。
概念
-
JVM内存模型
-
概念间的包含关系
新生代:包括Eden和From servivor和To servivor。
栈:包括虚拟机栈和本地方法栈。
堆:包括新生代和老年代。
JVM内存模型:包括堆(新生代和老年代),方法区(永久代)和栈(JVM栈和本地方法栈)。 -
职能
堆:线程共享区域。新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 可通过参数 –XX:NewRatio 进行配置 )。默认,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 进行配置 ),也就是: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
永久代:线程共享区域。JDK1.7其实是连续的内存空间,存储加载的类信息,常量,静态变量。它是JVM内存模型中存放类元数据信息的一种实现,也就是概念上叫的永久代或者方法区。JDK1.8 其实采用堆外内存实现的,可以理解为本地内存或者直接内存,这样跟JVM的内存模型的关联性就不大了。
程序计数器:线程执行的字节码文件的行号指示器。记录着程序执行过程的操作数。比如方法的入栈和出栈,变量的出栈和入栈操作。每个线程都具有各自独立的程序计数器,是非线程共享的内存区域。
虚拟机栈:每个方法被执行的时候都会创建一个"栈帧",用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
本地方法栈:服务Native方法。过程跟虚拟机栈相似。
堆外内存的两个应用实例
- Java NIO(HeapByteBuffer和DirectByteBuffer)
- JDK1.8的永久代的实现。
为什么要设计堆外内存?
- 是减少内存的拷贝次数吗?是。如果数据在磁盘上,要被应用程序使用是需要用户空间和内核空间的相互之间的数据拷贝过程。
- 如果把JVM应用程序的所有对象都放在了堆上,那么GC的开销就会变大。
- 如果应用程序需要使用堆上内存进行文件,网络IO时,会有内核空间和用户空间的拷贝。如果把文件和网络IO上的数据,放到堆外内存的话,直接让操作系统管理,既可以节约拷贝的时间,也可以减少GC的次数。
什么时候使用堆外内存?
- 长期存储且不会变化的。比如JDK中的类的元数据信息,常量。
- 稳定。避免Full GC 造成的暂时停止问题。
- 简单对象。像复杂的对象,需要一次编码,二次编码且过程需要大的时间和空间,也有可能可读性不好,就不能使用堆外内存。
- I/O效率。比如JDK中的NIO。比如Netty。
小结
- 本地内存:机子的物理内存。
- 直接内存:机子的物理内存。
- 堆上内存:伊甸园,新生代,老年代。
- 堆外内存:永久代和直接内存。
- 永久代内存:是JVM内存模型的一部分,是单独分配的并且内存空间是连续的。
- 由直接内存的概念,而产生的零拷贝知识。
- 网络编程为什么注重I/O效率?因为套接字其实就是I/O设备。