JVM虚拟机
1. JVM在运行时主要包含java堆,方法区,程序计数器,JVM栈,本地方法栈。其中JAVA堆和方法区归所有线程共有,而JVM栈和程序计数器归线程私有。
程序计数器
- 程序计数器主要是执行线程中的指令,在多核环境中,线程需要进行切换。线程获得CPU的执行后要恢复到原来的执行位置,这就是需要程序计数器保存当前线程执行位置。每个线程都有自己独立的计数器。
- 如果线程正在执行一个JAVA方法,则程序计数器记录的是当前字节码指令。
虚拟机栈
- JAVA虚拟机栈是线程私有的,生命周期与线程相同。虚拟机栈描述的是java方法的内存模型。每个方法在执行时,线程都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
1.1 局部变量表存储了基本类型和引用类型和returnAddress类型。局部变量表的空间在编译期间已经确定,在方法运行期间不会改变。 - 每一个JAVA方法的执行实际上就是栈帧在的入栈和出栈操作。
本地方法栈
- 本地方法栈是JVM虚拟机用来调用本地方法,不同的虚拟机实现本地方法栈不同。
JAVA堆
- JAVA堆是JVM内存管理中最大的一块内存区域,是所有线程共享的区域。堆在JVM启动时创建,主要存放对象实例和数组。JAVA堆可以处于物理上不连续的空间,只要逻辑上连续即可。
- 现在的垃圾收集主要采用分代收集,通常将JAVA堆分为新生代和老年代。其中,新生代又分为Eden区,from surivior区,to surivior区。通常Eden区和surivior区比例为8:2。
- 堆的大小都是可以控制的,通过-Xms和-Xmx进行控制。
方法区
- 方法区同java堆一样,都是所有线程共享的区域,主要存储被加载类的信息,常亮,静态变量,及时编译器变异后的代码。
- 对于HotSpot而言,GC分代收集时,会回收方法区,也称方法区为”永久代”。这样垃圾收集器就可以像管理java堆一样管理方法区的内存。
- 垃圾收集行为在方法区是很少出现的,即使出现了,回收内存成果也是不怎么好。在方法区进行垃圾回收主要是回收常亮池和类型的卸载。
运行时常量池
- 运行时常量池是方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等信息,还有一项信息就是常量池。
- 常量池另外一个重要特征就是动态性,运行期间也可以将新的常量放入池中。常量池中主要存放符号引用和各种字面量。
直接内存
- JDK中NIO,采用了通道和缓冲一种新的I/O方式,NIO可以采用Native函数直接分配堆内存以外的内存。即不占用JVM内存。
- 在JVM中同时存储了一个DirectByteBuffer对象,它可以直接操纵直接内存。DirectByteBuffer对象的引用指向直接内存。
- 直接内存的存在避免了JVM和本地堆中来回复制数据。
对象创建
- 在应用程序中当生成一条New指令时,JVM就会从常量池中寻找代表该类型的符号引用,如果能找到该类的类型符号,则说明已经加载该类。如果没有找到,则需要重新加载该符号代表的类。
- 找到代表该类的符号引用,JVM会分配一块与该对象大小相等内存空间。分配内存空间时,可以采用指针碰撞法或空闲列表方式。
![这里写图片描述](https://img-blog.csdn.net/20171031150156458?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanVuY2hlbmJiMDQzMA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
对象访问定位
- 在JVM堆中生成对象之后,访问对象的方式主要通过reference来访问。在JVM中reference类型在JVM规范中只规定了一个指向对象的引用,但是没有规定通过何种方式去定位,查找到对象。
- 目前主流的reference访问对象方式主要有通过句柄和直接指针两种方式。在流行的HotSpot中,采用直接指针方式进行定位对象。
2.1 直接指针访问是指 reference中存放了对象实例的指针(即对象地址)和对象类型数据指针的地址,通过reference可以直接在堆中定位到对象实例。对象类型的指针也是存放在堆中,但是该类型数据存放在方法区中。说起来有点拗口,通过图可以直观表示一下:
2.2 句柄方式访问是指在JVM堆中单独分配一块内存,用来存放句柄池。句柄池中存放的是都是句柄,而句柄中包含了对象实例数据与对象类型数据的地址信息。reference中存放的是句柄的地址。
2.2.1 上图可以看出,使用句柄访问对象时,reference中存放的是句柄的地址,句柄是存放在JVM堆中的句柄池中,句柄中存放了对象数据实例指针和对象类型数据指针。