视频讲解:https://www.bilibili.com/video/BV16J411h7Rd?p=21
JVM内存模型非常好的总结帖:https://mp.weixin.qq.com/s/lryDeCfiacRxCMpzEmyoLA
方法区与常量池
方法区存着类的信息,常量和静态变量,即类被编译后的数据。这个说法其实是没问题的,只是太笼统了。更加详细一点的说法是方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用(类的全限定名、字段名和属性、方法名和属性)。
![](https://img.haomeiwen.com/i19904539/570e16788e2ddf7d.png)
静态常量池和动态常量池的关系以及区别
静态常量池存储的是当class文件被java虚拟机编译后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。
动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。
总结
方法区里存储着class文件的信息和动态常量池,class文件的信息包括类信息和静态常量池。可以将类的信息是对class文件内容的一个框架,里面具体的内容通过常量池来存储。
动态常量池里的内容除了是静态常量池里的内容外,还将静态常量池里的符号引用转变为直接引用,而且动态常量池里的内容是能动态添加的。例如调用String的intern方法就能将string的值添加到String常量池中,这里String常量池是包含在动态常量池里的,但在jdk1.8后,将String常量池放到了堆中。
String与常量池
String 类型的常量池比较特殊。它的主要使用方法有两种:
- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。
- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象
java堆中对象探秘
对象的创建方法:指针碰撞、空闲列表
- 指针碰撞:对象所需内存的大小在类加载完成之后便可完全确定,假设java堆中的内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放到另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间挪动一段与对象大小相等的距离,这种方式就被称为指针碰撞。
- 如果java堆中的空间不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没办法简单的指针碰撞了,虚拟机就会维护一个列表,记录上哪些内存是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种方式被称为空闲表法。
对象的内存布局
对象在堆内存中可以划分为三个部分:对象头、实例数据和对齐填充
-
对象头(32位虚拟机为例)
对象头包括两部分信息。第一类是存储对象自身的运行数据,如哈希码(hashcode)、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等;第二部分是类型指针,即对象指向它的类型原数据的指针,java虚拟机通过这个指针来确定该对象是哪个类的实例。
普通对象
数组对象和Mark Word
64位Mark Word
-
实例数据:对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录下来。
-
对其填充:不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。如果对象没有对齐的话,就需要通过对其填充来补全。
对象的访问定位
主流的访问方式主要有两种:句柄、直接指针
- 如果使用句柄访问的话,java队中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息;
- 如果使用直接指针访问的话,java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只访问对象本身的话,就不需要多一次的间接访问的开销。
这两种方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改;使用直接指针来访问的最大好处就是速度快,它节省了一次指针定位的时间开销,由于对象访问在java中非常频繁,因此这类开销积少成多就是一项极为可观的执行成本。
网友评论