最近开始看《深入理解JVM虚拟机》。为了记得更牢固,方便以后复习。做好读书笔记。
Jvm内存模型.png- 如果当前线程执行的是一个Java方法,那么计数器记录的是它执行字节码指令的位置。如果执行的是本地Native方法,那么计数器值值为空。另外,此内存区域是JVM虚拟机中唯一一个没有规定任何OutOfMemoryError的区域。
- 如果请求的栈深度超过虚拟机所允许的栈深入,会抛出StackOverflowError。如果虚拟机栈可动态扩展(大多数的虚拟机都可动态扩展),当扩展无法申请到足够的内存时,就会抛出OutOfMemoryError。
- 和虚拟机栈一样会抛出StackOverflowError和OutOfMemoryError。
- 一般来说,所有的对象实例和数组都要在堆上分配。但是随着JIT编译器的发展和逃逸分析技术的日渐成熟,所有的对象都分配上堆上也就不那么绝对了。如果堆的内存不够,无法再为新的实例分配内存,且堆无法扩展时(大部分虚拟机都是可扩展的,通过-Xmx和-Xms控制),会抛出OutOfMemoryError。
- 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError。
除了以上内存块,还要补充一个 直接内存
直接内存
直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内也被频繁的使用,而且也可能导致OutOfMemoryError异常出现。例如JDK1.4中新加入的NIO就是使用Native函数库分配对外内存。
对象的创建
虚拟机遇到一条new指令的时候,会先去检查这个指令能否常量池在常量池定位到一个符号引用,如果得到这个符号引用,就检查它代表的类是否已经被加载、解析、初始化过(初始化静态变量,执行static块代码等)。如果没有,那需要先执行类加载过程。
在类加载完成后,就为新的实例对象分配堆内存。所需的内存大小在类加载完成后就已经完全确定了。为对象实例分配空间其实就是从Java堆中划分一块确定大小的内存出来。
一些要点:
- java堆如何给实例分配空间的
- 每个对象分配的内存空间是固定的。
- 怎么解决分配空间时遇到的并发问题——(同步和TLAB缓存)
- 使用CAS进行分配内存(失败后重试)
- 使用TLAB进行分配。TLAB是每个线程都会分配到的一部分内存空间(也属于堆内存的一部分),先在这块内存上分配,TLAB内存块满了后,将TLAB同步到堆内存中去(同步的过程是加锁的,所以是线程安全的),然后新开一块新的TLAB空间给线程。
对象的内存布局
实例内存空间分三大块:对象头、实例数据、对齐填充
- 消息头存储的一部分是对象运行时数据(如hash码、GC分带年龄、锁状态标志等,32位系统架构下就32bit),还有一部分时指向它所属类的指针
- 实例数据部分就是真正的存储数据了。包括父类继承下来的信息
- 对齐填充不是一定存在的。因为要求HotSpot要求对象起始位置必须是8字节的整数倍。所以不够就填充。
对象的访问定位
- 通过句柄访问——需要在java堆中管理一个句柄池
- 直接指针访问——HotSpot采用这种
字符串池的理解
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
在jdk6下面两个都是输出false,jdk7的时候一个 true false.
产生差异的原因是: 在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代,返回的也是永久代的这个实例的引用,而由StringBuilder创建的字符串实例在java堆上,所以永远都是false。
jdk7中,intern()方法不在复制实例,只是在常量池中记录首次出现的实例引用,因此intern()方法返回的和由StringBuilder.创建的字符串是同一个。
由于java这个常量是虚拟机内置的,所以不符合首次出现的实例,指向的就不一样。
网友评论