Java的指令都是根据栈来设计的,栈是运行时的单位,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。
是的,栈的生命周期跟线程的生命周期一致,线程结束后栈也会释放
每个线程都有一个虚拟机栈,栈的内部是一个个栈帧,每个栈帧由局部变量表、操作数栈、动态链接、方法返回地址构成
会,栈有两种异常状况,StackOverflowError和OutOfMemoryError异常。StackOverflowError为栈的深度不足,通俗的解释就是栈帧大小的综合大于了-Xss配置的值,OutOfMemoryError则是线程创建时需要分配给它一个私有栈而内存空间不足所发生的异常。
不会,栈是线程私有的,生命周期随着线程的结束而结束,所以不需要GC来进行回收。
Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
局部变量表的容量以变量槽(Variable Slot,下称 Slot)为最小单位,如果访问的是 32 位数据类型的变量,索引 n 就代表了使用第 n 个 Slot,如果是 64 位数据类型的变量,则说明会同时使用 n 和 n+1 两个 Slot。
内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个 java 虚拟机规范没有规定任何 OOM 情况的区域。
使用java指令的地方,用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
本地方法栈和虚拟机栈类似,只不过本地方法栈为 Native 方法服务。
当某个线程调用一个本地方法时,它就进入了一个全新的并且不收虚拟机限制的世界。它和虚拟机拥有同样的权限。
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。即方法区里存放着类的版本、字段、方法、接口和常量池(存储字面量和符号引用)。
《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它,在JDK1.7版本之前永久代是对方法区的实现。永久代是基于 HotSpot虚拟机
1、类的权限定名;2、字段名和属性;3、方法名和属性。
1、类型信息:
类的完整名称
类的直接父类的完整名称
类的直接实现接口的有序列表
类型标志(类类型还是接口类型)
类的修饰符(public private defautl abstract final static)
2、类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(字符串、整数、浮点数)和对其他类型、字段、方法的符号引用。
3、字段信息(该类声明的所有字段)
字段修饰符(public、peotect、private、default)
字段的类型
字段名称
4、方法信息
方法信息中包含类的所有方法。
方法修饰符
方法返回类型
方法名
方法参数个数、类型、顺序等
方法字节码
操作数栈和该方法在栈帧中的局部变量区大小
异常表
5、类变量(静态变量)
6、指向类加载器的引用
7、指向Class实例的引用
8、方法表
9、运行时常量池(Runtime Constant Pool)
java虚拟机规范中明确说明:"尽管所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾回收或进行压缩",但是对于HotSpot JVM 而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆区分开来
所以,方法区看作是一块独立于堆的内存空间
JDK1.7及以前
JDK1.8以及后
java虚拟机被允许对满足上述3个条件的无用类进行回收,这里说仅仅是"被允许",而不是和对象一样,没有引用了就必然进行回收,关于是否要对类型进行回收,HotSpot虚拟机提供了-Xonclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading,-XX:+TraceClassUnLoading查看类加载和卸载信息
在大量使用反射动态代理CGLIb等字节码框架,动态生成JSP以及OSGI这类频繁自定义类加载器场景中,通常都需要java虚拟机具备类型卸载的能了,以保证不会对方法区造成过大的内存压力
jdk7中将StringTbale放到了堆空间,因为永久代中回收频率很低,在Full GC 时候才会回收,而Full GC是老年代的空间不足,永久代空间不足时才会触发,这就导致StringTable回收频率不高,而我们开发中会有大量字符串创建,回收效率低导致永久代内存不足,放到堆里能及时回收内存
对象头
实例数据(InstanceData)
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
对齐填充(Padding)
不是必须,也没有特别含义,仅仅起到占位符的作用
由于reference类型在java虚拟机规范中并没有定义这个引用应该通过什么方式去定位,所以对象访问方式也是由虚拟机自己决定的,主流的访问方式主要有两种:直接指针和句柄访问
句柄访问
直接指针
这两种方式各有优势,使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾回收时移动对象是非常普遍的行为)时只需要修改句柄的实例指针,而reference本身不需要修改,而使用直接指针访问的最大好处就是快,因为少一次指针定位的时间开销,在HotSpot虚拟机中采用的是直接指针
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中的内存区域,在JDK1.4中新加入了NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,这样能在一些场景中显著提升性能,因为避免了在java堆和native堆中来回复制数据,直接内存分配不会受到java堆大小的印象,但是既然是内存,则肯定会受到本机内存大小的限制,如果内存区域大于物理内存限制,则会抛出OOM异常
直接内存大小可以通过MaxDirectMemorySize设置,如果不指定,默认大小与堆的最大值-Xmx参数值一致
Class文件常量池(class constant pool)
Class 文件常量池指的是编译生成的 class 字节码文件,其结构中有一项是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
运行时常量池
运行时常量池是方法区的一部分,是一块内存区域
。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。
字符串常量池(string pool也有叫做string literal pool)
字符串常量池是全局的
,JVM 中独此一份,因此也称为全局字符串常量池
。运行时常量池中的字符串字面量若是成员的,则在类的加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。其实,“使用常量池”对应的字节码是一个 ldc 指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7)。String 类的 intern() 方法还可在运行期间把字符串放到字符串常量池中。JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。
其中:
上图中,刻画了Java程序运行时的堆空间,可以简述成如下2条
1.JVM中共享数据空间可以分成三个大区,新生代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation),其中JVM堆分为新生代和老年代,新生代跟老年代的比例默认为 1:2
2.新生代可以划分为三个区,Eden区(存放新生对象),两个幸存区(From Survivor和To Survivor)(存放每次垃圾回收后存活的对象)他们的内存大小默认为 8:1:1
3.永久代管理class文件、静态对象、属性等(JVM uses a separate region of memory, called the Permanent Generation (orPermGen for short), to hold internal representations of java classes. PermGen is also used to store more information )(在jdk1.8后移除了永久代)
4.JVM垃圾回收机制采用“分代收集”:新生代采用复制算法,老年代采用标记清理算法。
是的,几乎所以的对象实例都是在堆上分配内存,在栈里存放的是对象的引用地址
-Xms和-Xmx不作用于永久代
“-Xms”用于表示堆区的起始内存,等价于-XX:InitialHeapSize “-Xmx”则用于表示堆区的最大内存,等价于-XX:MaxHeapSize
.默认情况下,初始内存大小:物理电脑内存大小 / 64;最大内存大小:物理电脑内存大小 / 4