JVM构成
JVM包括两个子系统和两个组件。
- 两个子系统为:
- class loader(类装载)
- Execution engine(执行引擎)
- 两个组件为:
- Runtime data area(运行时数据区)
- Native interface(本地接口)
-
Class loader功能
- 根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。程序中可以extends java.lang.ClassLoader类来实现自己的Class loader。 Execution engine功能
- 执行classes中的指令。任何JVM specification实现(JDK)的核心都是Execution engine,不同的JDK例如Sun的JDK和IBM的JDK好坏主要就取决于他们各自实现的Execution engine的好坏。 Native interface组件
- 与native libraries交互,是其它编程语言交互的接口。当调用native方法的时候,就进入了一个全新的并且不再受虚拟机限制的世界,所以也很容易出现JVM无法控制的 native heap OutOfMemory。 Runtime Data Area组件
- 这就是我们常说的 JVM的内存。
主要分为五个部分:
线程共享区域
- Heap (堆):一个Java虚拟实例中只存在一个堆空间。不连续
- 新生代
- Eden
- From Survivor
- To Suvivor
- 老年代
- 线程私有的分配缓冲区Thread Local Allocation Buffer
- Method Area(方法区域):存储被加载的class的信息、常量、静态变量、JIT后的代码。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。
- 存储
- 被加载过的类信息(版本,字段,方法,接口等)
- 常量
- 静态常量
- JIT后的代码
- HotSpot中,用Permanent Generation来实现方法区. JDK 1.8中,改用Native Memeory实现方法区,叫MetaSpace
- 运行时常量池(Runtime Constant Pool)
线程私有/隔离区域
- Java Virtual Machine Stack(java VM的栈):虚拟机只会直接对Java stack执行两种操作:以帧为单位的压栈或出栈。描述Java方法执行的内存模型
- 栈帧(Stack Frame)
- 局部变量表
- 基本数据类型
- 对象引用
- returnAddress类型,指向一条字节码指令地址
- 操作数栈
- 动态链接
- 方法出口
- Program Counter Register(程序计数器):每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。计数器的内容总是指向下一条将被执行指令的地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
- Java多线程使用线程切换并分配CPU执行时间的方式来实现,需要记录每个线程执行的位置
- Java方法的话,记录正在执行的虚拟机字节码指令地址;执行Native方法,计数器的值为undefined
- 唯一没有OOM的区域
- Native method stack(本地方法栈):保存native方法进入区域的地址
以上五部分只有Heap和Method Area是被所有线程的共享使用的;
而Java stack, Program counter 和Native method stack是以线程为粒度的,每个线程独自拥有自己的部分。
自己在原文上作了修改
原文链接如下: http://www.javaranger.com/archives/462
直接内存(Direct Memory)
不是JVM的运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但也被频繁使用
NIO引入基于Channel和Buffer的非阻塞IO,它可以使用Native函数库,直接分配堆外内存,然后通过Java堆内存中的DirectByteBuffer对象,作为这块内存的引用进行操作
这样可以顯著提高性能,因为避免在Java堆和Native堆中来回复制数据
显然,本机直接内存的分配不会受到Java堆内存大小的限制,但是会受到本机总内存大小(包括RAM以及SWAP或分页文件),以及处理器寻址空间的限制。
设置-Xmx最大堆内存大小时,要考虑直接内存,保证各个内存区域总和小于等于物理内存限制,包括物理的和操作系统级别的限制,否则会在动态扩展内存空间时出现OOM