Java虚拟机知识点总结(一)
1、Java内存区域
1.1 Java内存区域
1、程序计数器
程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条字节码指令,分支,循环,跳转,异常处理,线程恢复等功能都依赖这个计数器来完成。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
2、java虚拟机栈
与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型。 每个方法的执行都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法的调用到执行完成的过程,就是对应着一个栈在虚拟机栈中的入栈和出栈的过程。
局部变量表:存放编译期可知的各种基本数据类型,引用类型,局部变量表的大小在编译期便已经可以确定,在运行时期不会发生改变 。
栈的大小:如果栈满了,StackOverFlowError,递归调用很常见。 如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError 异常。
3、本地方法栈
和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行Java方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。 和虚拟机一样,会抛出StackOverflowError 和 OutOfMemoryError 异常。
4、java堆
Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:在细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
5、方法区
和java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(类的版本、字段、方法、接口) 、常量、静态变量、即时编译器编译后的代码等数据。别名:非堆。
6、运行时常量池
运行时常量池是方法区的一部分。Class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行常量池中存放。
实例:
public class Changliang {
public static void main(String[] args) {
// s1与s2是相等的,为字节码常常量
String s1 = "abc";
String s2 = "abc";
// s3创建在堆内存中
String s3 = new String("abc");
// intern方法可以将对象变为运行时常量
// intern是一个native方法
System.out.println(s1 == s3.intern()); // true
}
Java语言并不要求常量一定只有编译期才能产生,运行时期也可能将新的常量放入池中,例如可以使用String类的intern()方法。
7、直接内存
jdk1.4中增加了NIO,可以分配堆外内存(系统内存替代用户内存),提高了性能。
1.2 虚拟机对象
1、对象的创建过程
2、如何在堆中给对象分配内存
(1)指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存度放一边,空闲的内存放另一边**,中间放着一个指针作为分界点的指示器**,所分配内存就仅仅是把哪个指针向空闲空间那边挪动一段与对象大小相等的举例,这种分配方案就叫指针碰撞
(2)空闲列表:有一个列表,其中记录中哪些内存块有用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,然后更新列表中的记录,这就叫做空闲列表。
(3)线程安全问题:在两个线程同时创建对象时,可能会造成空间分配的冲突,解决方案有:线程同步(但执行效率过低)或给每一个线程单独分配一个堆区域TLAB Thread Local Allocation Buffer(本地线程分配缓冲) 。
3、对象的内存布局
(1)对象头(Header)
自身运行时数据(32位~64位 MarkWord):哈希值、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳
类型指针(什么类的实例)
(2)实例数据(InstanceData )
对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。
(3)对齐填充(Padding)
占位符填充内存
4、对象的访问定位
(1)使用句柄
如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
(2)直接指针
如果使用直接指针访问,那么Java堆对像的布局中就必须考虑如何防止访问类型数据的相关信息,reference中存储的直接就是对象的地址。
(3)对比
使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销