读书笔记:深入理解Java虚拟机_JVM高级特性与最佳实践_笔记二_自动内存管理机制

第二部分 自动内存管理机制

第二章 Java内存区域与内存异常

由于Java把内存控制的权利交给了虚拟机所以一旦出现内存泄漏和溢出的问题,不了解虚拟机如何管理内存,则排查错误会变困难

2.2运行时的数据区域

Java虚拟机在执行Java程序中会根据不同的用途把内存划分为不同的几个数据区域 ,这些区域有的生命周期依赖于虚拟机进程,有的生命周期依赖于用户进程
Java虚拟机包括以下几个运行时的数据区域:
图:Java运行时数据区

2.2.1 程序计数器

1.线程私有
2.是较小的内存空间:用法就是字节码解释器通过改变计数器来取得下一条字节码指令(如分支、循环、跳转、异常处理、线程恢复等基础功能)
3.Java虚拟机多线程:通过线程轮换并分配处理器的执行时间的方式来实现(每条线程一个独立的计数器,所以多核处理器通过快速切换计数器达到多线程)
4.如果线程正在执行java文件则计数器指向的是该虚拟机字节码指定的地址,如果是Native方法(通过Native可以指向非java语言代码–接口)这计数器为空
5.该区域不存在outOfMemoryError情况的区域

2.2.2 Java虚拟机栈
  1. 线程私有,生命周期和线程相同
  2. 且生命周期和线程一样
  3. 方法执行时候会创建栈帧用于保存局部变量表(基础类型和引用对象–并不代表对象本身可以看作其句柄(遥控器) long和double占2个局部变量空间)、操作数栈、动态链接、方法出口
    且一个方法的执行到结束就是栈帧入栈到出栈的过程。
    ps:栈内存代表的是:虚拟机栈中局部变量表的部分
2.2.3本地方法栈

使用Native调用其他语言,形态类似于虚拟机栈

2.2.4 Java堆
  1. 是虚拟机管理内存中最大的一块
  2. 其线程是公有的
  3. 唯一目的就是保存对象实例(由于一些优化技术使其没有那么绝对)
  4. Java堆是垃圾收集器的主要区域

ps栈和堆存放数据的区别(引用于百度知道)
栈内存用于存放局部变量,堆内存是在程序执行过程中动态的进行内存分配,对象都是放在堆内存中,因为它是在程序执行过程中动态创建的,而引用如果是作为局部变量是放在栈内存中的,如果它作为一个对象的成员变量则它是跟这个对象一起放在堆内存中的

2.2.5方法区:
  1. 该区域是线程公有的。
  2. 该区域用于存放虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据
  3. 使用永生代的概念去实现方法区

对于新生代,老年代和永生代的概念:
http://www.cnblogs.com/wuhan729/p/8376615.html

2.2.6运行时常量池

https://www.cnblogs.com/dreamroute/p/5946272.html
1. 该区域是方法区的一部分
2. 运行时的常量池的例子:

String a ="helloworld";
String b ="helloworld";
String c =new String("helloworld");
String d ="hello";
String e ="world";
String f = "hello"+"world";
String g ="hello"+new String("world");
System.out.println(a==b);//true 1
System.out.println(a==c);//false 2
System.out.println(a==d+e);//false 3
System.out.println(a == f);//true 4
System.out.println(f == d+e);//false 5
System.out.println(g == g.intern());//intern会规范字符串//false 6
System.out.println(a == g.intern());//true 7

解析:

  • 情况一:当创建String a =”helloworld”时虚拟机会把helloworld这个字面量放到常量池里面,而a作为引用(引用存在局部变量表栈中)指向这个常量池中的字面量所以为true
  • 情况二:c new了一个String 属于对象实例这个helloworld应该保存在堆的某个区域中 而a中的helloworld保存在常量池中属于方法区中的一块所以为false
  • 情况三:d 和 e分别指向常量池中不同的区域,当d+e其实存放到堆中某个区域。所以情况三为false
  • 情况四:因为f的两个字符串相加是可预知的(不像d和e是通过两个引用做相加)由于虚拟机优化会先将两个字符串合并后在常量池中寻找,如果存在直接引用到这个字面量
  • 情况六和情况七:String的函数intern的作用是:用于规范字符串,调用这个方法会首先检查字符串池中是否有”helloworld”这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。

对于intern像书中介绍运行时的常量池对于Class文件常量池另一个特征是具有动态性,Java语言并不要求常量必须在编译前才能产生,也就是并非预置入Class常量池才能进入方法区中的运行时常量池,运行期间也可进入,这种运用最多的就是String的intern方法

2.2.7直接内存

https://www.cnblogs.com/xing901022/p/5243657.html
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。
本机直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制
配置虚拟机参数时,不要忽略直接内存 防止出现OutOfMemoryError异常
直接内存(堆外内存)与堆内存比较
直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

2.3 HotSpot虚拟机对象探秘

该章节深入探讨HotSpot虚拟机在Java堆中对象分配、布局和访问的全过程

2.3.1对象的创建

简介本段讨论普通的Java对象在虚拟机层次的创建过程
这里写图片描述
TLAB:由于对象创建在虚拟机中是非常频繁的过程,所以为了保证线程安全提出了两种方案:
1.采用CAS配上失败重试
2.使用TLAB(本地分配缓冲–为每个线程分配一个缓冲区)哪个线程需要分配内存就在自己的缓冲区分配
https://www.jianshu.com/p/cd85098cca39 https://blog.csdn.net/xiaomingdetianxia/article/details/77688945
这里写图片描述
以上工作完成后,虚拟机的new对象工作已经完成,但是对于程序来说对象创建才刚刚开始(init方法还未执行)字段也都为空值。

2.3.2对象内存布局

对象在内存中的存储布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
分配策略为:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Point 对象指针)相同宽度的字段一般都被分配在一起
在这个前提下,父类定义在子类之前,如果CompactFields的true,子类较窄的变量可能会插到父类的变量空隙之中。

2.3.3对象的访问定位

建立对象后如何使用这个对象呢,我们需要通过栈上的reference数据来操作堆上的具体对象。而对象的访问方式取决于虚拟机的实现而定的。
目前主流的两种访问方式有:句柄和直接指针。
这里写图片描述
这里写图片描述
两种策略的优缺点:

  • 使用句柄访问:修改更易,直接修改句柄指针指向
    使用直接指针访问:访问速度更快(省去一次指针定位的时间)
  • 这里HotSpot主要使用直接指针访问的形式

猜你喜欢

转载自blog.csdn.net/weixin_40763557/article/details/81187900