《深入理解java虚拟机》学习笔记--第二章:自动内存管理机制 java虚拟机读书JVM
程序员文章站
2022-03-10 12:39:43
...
说来惭愧,之前一直没有写过博客,学习的内容或者一些技术类话题也只是记录在云笔记上,今天偶然间想起来这件事情,从今天开始不定时更新博客,如有不正确或者待讨论的地方欢迎看到的朋友评论。
技术分享从学习周志明的《深入理解java虚拟机》开始吧。文章内容为本人读书笔记,如有雷同,那我们真是太有缘了。
下面进入正文:
=================================================================
JVM中运行时数据区:方法区、虚拟机栈、本地方法栈、堆、程序计数器
1.程序计数器:
(1)较小的内存空间,相当于当前线程执行字节码的行号指示器,字节码解释器工作时就是通过改变该计数器的值来执行下一条需要执行的字节码指令(分支、循环、跳转、异常处理、恢复线程)。
(2)在一个确定的时刻,一个核只处理一条线程中的指令,为了线程切换后可以恢复到正确的位置,因此每个线程都有一个独立的程序计数器,互不影响,独立存储,这类内存区域称为线程私有内存。
(3)若线程执行的是java方法,则计数器记录的是当前执行的字节码地址,若执行的是native方法,则计数器为空(undefined),此内存区域是java规范中唯一没有规定oom情形的区域。
2.java虚拟机栈:
(1)线程私有内存,其生命周期与线程相同;虚拟机栈描述java方法执行的内存模型:每个方法执行的同时,会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法执行的过程就对应栈帧从入栈到出栈的过程。
(2)局部变量表存放了编译期间可知的各种基本数据类型(boolean,byte,char,short,int,long,float,double),对象的引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址);其中long和double占用两个局部变量空间(slot),其余均为一个。
(3)规定两种异常情形:1.如果线程请求的栈深度大于栈允许的深度,报stack over flow;2.如果虚拟机在扩展栈时无法申请到足够的内存,报oom异常。
3.本地方法栈:
(1)与虚拟机栈非常类似,他们之间的区别在于本地方法栈为native方法服务;与虚拟机栈一样,也会抛出out of memory和stack over flow的异常。
4.java堆:
(1)一般来说,java堆是虚拟机中最大的一块内存,是线程共享的,在虚拟机启动的时候创建,唯一的目的就是存放对象实例(几乎所有的对象实例)。
(2)是垃圾回收的主要区域,只需要逻辑连续即可,当堆无法扩展时,抛出out of memory。
5.方法区:
(1)是各线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(2)只需要逻辑内存连续即可,可以选择固定大小也可以选择扩展,还可以选择不进行垃圾回收。当方法区无法满足内存分配需求时,将抛出oom。
(3)其中包括运行时常量池:存放编译器生成的各种字面量和符号引用。
6.直接内存:
(1)并不是虚拟机运行时的数据区域,也不是java虚拟机规范中的内存区域。但是这部分内存也被频繁使用,也可能出现oom异常。
(2)在jdk1.4引入了基于通道与缓冲区的I/O方式,可以使用native函数直接分配堆外内存。通过在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
HotSpoot虚拟机对象探秘:对象的创建、对象的内存访问、对象的访问定位
1.对象的创建:
(1)遇到new指令,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类的符号引用代表的类是否已被加载、解析和初始化过。如果没有限制性类还在过程。
(2)为新生对象分配内存,对象所需要的内存大小在类加载后已经可以确定。
(3)执行完上述步骤,在虚拟机的角度已经产生了一个新对象了,从java程序来看,还需要初始化对象,因此在new指令执行后一般还需要执行<init>方法,这样一个对象才完全生产出来。
2.对象的内存访问:
(1)对象在内存中存储氛围3块区域:对象头(Header)、实例数据(InstanceData)和对齐填充(padding)。
(2)对象头包括两部分信息:对象自身运行时数据、类型指针。
(3)对象自身运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
(4)类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。如果对象是数组,那么对象头中还有一个数据用于记录数组的长度。
3.对象的访问定位:
(1)java程序通过reference数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
(2)使用句柄的方式:java堆中划出一块内存作为句柄池,reference对象中存放的是句柄的地址,句柄中包含了对象实例数据与类型数据各自具体的地址。
(3)直接指针访问:reference中存的直接就是对象的地址,而对象类型数据的指针则存放在对象中。
小结一下:JVM运行时内存分为:程序计数器、java堆、方法区、java虚拟机栈、本地方法栈;其中程序计数器是线程私有内存;其他线程私有内存的还有java虚拟机栈、本地方法栈。程序计数器,相当于程序执行的指针,记录程序执行到哪一行,以及确定下一条指令的内容;虚拟机栈存放一些局部变量、操作数栈、动态链接及方法出口信息等,本地方法栈就是与虚拟机栈类似,存放native方法的相关内容;java堆和方法区都是线程共享的内存区域,java堆就是存放对象的实例,几乎所有new出来的对象都是存放在这个区域的。方法区则存储已被虚拟机加载的类信息,常量信息及静态变量。
技术分享从学习周志明的《深入理解java虚拟机》开始吧。文章内容为本人读书笔记,如有雷同,那我们真是太有缘了。
下面进入正文:
=================================================================
JVM中运行时数据区:方法区、虚拟机栈、本地方法栈、堆、程序计数器
1.程序计数器:
(1)较小的内存空间,相当于当前线程执行字节码的行号指示器,字节码解释器工作时就是通过改变该计数器的值来执行下一条需要执行的字节码指令(分支、循环、跳转、异常处理、恢复线程)。
(2)在一个确定的时刻,一个核只处理一条线程中的指令,为了线程切换后可以恢复到正确的位置,因此每个线程都有一个独立的程序计数器,互不影响,独立存储,这类内存区域称为线程私有内存。
(3)若线程执行的是java方法,则计数器记录的是当前执行的字节码地址,若执行的是native方法,则计数器为空(undefined),此内存区域是java规范中唯一没有规定oom情形的区域。
2.java虚拟机栈:
(1)线程私有内存,其生命周期与线程相同;虚拟机栈描述java方法执行的内存模型:每个方法执行的同时,会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法执行的过程就对应栈帧从入栈到出栈的过程。
(2)局部变量表存放了编译期间可知的各种基本数据类型(boolean,byte,char,short,int,long,float,double),对象的引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址);其中long和double占用两个局部变量空间(slot),其余均为一个。
(3)规定两种异常情形:1.如果线程请求的栈深度大于栈允许的深度,报stack over flow;2.如果虚拟机在扩展栈时无法申请到足够的内存,报oom异常。
3.本地方法栈:
(1)与虚拟机栈非常类似,他们之间的区别在于本地方法栈为native方法服务;与虚拟机栈一样,也会抛出out of memory和stack over flow的异常。
4.java堆:
(1)一般来说,java堆是虚拟机中最大的一块内存,是线程共享的,在虚拟机启动的时候创建,唯一的目的就是存放对象实例(几乎所有的对象实例)。
(2)是垃圾回收的主要区域,只需要逻辑连续即可,当堆无法扩展时,抛出out of memory。
5.方法区:
(1)是各线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
(2)只需要逻辑内存连续即可,可以选择固定大小也可以选择扩展,还可以选择不进行垃圾回收。当方法区无法满足内存分配需求时,将抛出oom。
(3)其中包括运行时常量池:存放编译器生成的各种字面量和符号引用。
6.直接内存:
(1)并不是虚拟机运行时的数据区域,也不是java虚拟机规范中的内存区域。但是这部分内存也被频繁使用,也可能出现oom异常。
(2)在jdk1.4引入了基于通道与缓冲区的I/O方式,可以使用native函数直接分配堆外内存。通过在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
HotSpoot虚拟机对象探秘:对象的创建、对象的内存访问、对象的访问定位
1.对象的创建:
(1)遇到new指令,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类的符号引用代表的类是否已被加载、解析和初始化过。如果没有限制性类还在过程。
(2)为新生对象分配内存,对象所需要的内存大小在类加载后已经可以确定。
(3)执行完上述步骤,在虚拟机的角度已经产生了一个新对象了,从java程序来看,还需要初始化对象,因此在new指令执行后一般还需要执行<init>方法,这样一个对象才完全生产出来。
2.对象的内存访问:
(1)对象在内存中存储氛围3块区域:对象头(Header)、实例数据(InstanceData)和对齐填充(padding)。
(2)对象头包括两部分信息:对象自身运行时数据、类型指针。
(3)对象自身运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
(4)类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。如果对象是数组,那么对象头中还有一个数据用于记录数组的长度。
3.对象的访问定位:
(1)java程序通过reference数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。
(2)使用句柄的方式:java堆中划出一块内存作为句柄池,reference对象中存放的是句柄的地址,句柄中包含了对象实例数据与类型数据各自具体的地址。
(3)直接指针访问:reference中存的直接就是对象的地址,而对象类型数据的指针则存放在对象中。
小结一下:JVM运行时内存分为:程序计数器、java堆、方法区、java虚拟机栈、本地方法栈;其中程序计数器是线程私有内存;其他线程私有内存的还有java虚拟机栈、本地方法栈。程序计数器,相当于程序执行的指针,记录程序执行到哪一行,以及确定下一条指令的内容;虚拟机栈存放一些局部变量、操作数栈、动态链接及方法出口信息等,本地方法栈就是与虚拟机栈类似,存放native方法的相关内容;java堆和方法区都是线程共享的内存区域,java堆就是存放对象的实例,几乎所有new出来的对象都是存放在这个区域的。方法区则存储已被虚拟机加载的类信息,常量信息及静态变量。