2、深入理解JVM的内存区域

实例代码运行 JVM内存处理全流程
1、JVM向操作系统申请内存:JVM通过配置参数或默认参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地址分配给JVM,接下来JVM就进行内部分配
2、初始化运行时数据区:JVM获取内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小
-Xms30m -Xmx30m -Xss1m -XX:MaxMetaspaceSize=30m
3、类加载:主要是把class放入方法区,还有class中的静态变量和常量也要放入方法区。
4、执行方法及创建对象:启动main线程,执行main方法,开始执行第一行代码,此时堆内存中会创建一个student对象,对象引用 student就存放在栈中,后续代码中遇到new关键字,会再创建一个student对象,对象引用student也存放在栈中。
在这里插入图片描述
总结一下JVM运行内存的整体流程
JVM在操作系统上启动,申请内存,先进行运行时数据区的初始化,然后把类加载到方法区,最后执行方法。
方法的执行和退出过程在内存上的体现就是虚拟机栈中栈帧的入栈和出栈。
同时在方法的执行过程中创建的对象一般情况下都是放在堆中,最后堆中的对象也是需要进行垃圾回收清理的。

堆空间的分代划分
堆被划分为新生代和老年代,新生代 又被进一步划分为Eden和Survivor区,最后Survivor由From Survivor和To Survivor组成。
在这里插入图片描述
JHSDB工具

JHSDB工具是一款基于服务性代理实现的进程外调试工具。服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的,主要基于java语言实现在API集合。
JDK1.8的开启方式
jdk1.8启动JHSDB的时候必须将sawindbg.dll(一般在jdk的目录下)复制到对应目录的jre下。
在这里插入图片描述
然后到目录:C:\Program Files\Java\jdk1.8.0_101\lib 进入命令行,执行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
在这里插入图片描述
VM 参数加入: -XX:+UseConcMarkSweepGC
在这里插入图片描述
-XX:-UseCompressedOops
在这里插入图片描述
在这里插入图片描述
JHSDB中查看对象
实例代码 启动:因为 JVM 启动有一个进程,需要借助一个命令 jps 查找到对应程序的进程
在这里插入图片描述

在 JHSDB 工具中 attach 上去
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查看堆参数
在这里插入图片描述
查看对象
在这里插入图片描述
在这里插入图片描述
全路径名搜索
在这里插入图片描述
双击出现这个 Teacher 类的对象,两个,就是 T1 和 T2 对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
JHSDB中查看栈
在这里插入图片描述
在这里插入图片描述
从上图中可以验证栈内存,同时也可以验证到虚拟机栈和本地方法栈在 Hotspot 中是合二为一的实现了。
在这里插入图片描述
从底层深入理解运行时数据区(总结)
在这里插入图片描述
虚拟机内存优化技术->栈帧之间数据的共享
在一般的模型中,两个不同的栈帧的内存区域是独立的,但是大部分的 JVM 在实现中会进行一些优化,使得两个栈帧出现一部分重叠。(主要体现在方法 中有参数传递的情况),让下面栈帧的操作数栈和上面栈帧的部分局部变量重叠在一起,这样做不但节约了一部分空间,更加重要的是在进行方法调用时就可以直接公用一部分数据,无需进行额外的参数复制传递了。
在这里插入图片描述
使用 JHSDB 工具查看栈空间一样可以看到
在这里插入图片描述
内存溢出
1、栈溢出
参数:-Xss1m, 具体默认值需要查看官网:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BABHDABI
在这里插入图片描述
HotSpot 版本中栈的大小是固定的,是不支持拓展的。 java.lang.StackOverflowError 一般的方法调用是很难出现的,如果出现了可能会是无限递归。 虚拟机栈带给我们的启示:方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有
存在的意义。递归代码简洁,非递归代码复杂但是速度较快。 OutOfMemoryError:不断建立线程,JVM 申请栈内存,机器没有足够的内存。
同时要注意,栈区的空间 JVM 没有办法去限制的,因为 JVM 在运行过程中会有线程不断的运行,没办法限制,所以只限制单个虚拟机栈的大小。
2、堆溢出
内存溢出:申请内存空间,超出最大堆内存空间。
如果是内存溢出,则通过 调大 -Xms,-Xmx 参数。
如果不是内存泄漏,就是说内存中的对象确实都是必须存活的,那么就应该检查 JVM 的堆参数设置,与机器的内存对比,看是否还有可以调整的空间, 再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行时的内存消耗。
3、方法区溢出
(1) 运行时常量池溢出
(2)方法区中保存的 Class 对象没有被及时回收掉或者 Class 信息占用的内存超过了我们配置。
注意 Class 要被回收,条件比较苛刻(仅仅是可以,不代表必然,因为还有一些参数可以进行控制):
(1)、该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
(2)、 加载该类的 ClassLoader 已经被回收。
(3)、 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
在这里插入图片描述
4、本机直接内存溢出
直接内存的容量可以通过 MaxDirectMemorySize 来设置(默认与堆内存最大值一样),所以也会出现 OOM 异常; 由直接内存导致的内存溢出,一个比较明显的特征是在 HeapDump 文件中不会看见有什么明显的异常情况,如果发生了 OOM,同时 Dump 文件很小,可 以考虑重点排查下直接内存方面的原因。

常量池
1、Class 常量池(静态常量池):存放编译时的字面量,符号引用,class方法。
在 class 文件中除了有类的版本、字段、方法和接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期间生成的各种字面 量和符号引用。
在这里插入图片描述
(1)字面量:给基本类型变量赋值的方式就叫做字面量或者字面值。
比如:String a=“b” ,这里“b”就是字符串字面量,同样类推还有整数字面值、浮点类型字面量、字符字面量。
(2)符号引用:以一组符号来描述所引用的目标。
符号引用可以是任何形式的字面量,JAVA 在编译的时候一个每个 java 类都会被编译成一个 class 文件,但在编译的时候虚拟机并不知道所引用类的地址(实际地址),就用符号引用来代替,而在类的解析阶段就是为了把 这个符号引用转化成为真正的地址的阶段。
一个 java 类(假设为 People 类)被编译成一个 class 文件时,如果 People 类引用了 Tool 类,但是在编译时 People 类并不知道引用类的实际内存地址,因 此只能使用符号引用(org.simple.Tool)来代替。而在类装载器装载 People 类时,此时可以通过虚拟机获取 Tool 类的实际内存地址,因此便可以既将符号 org.simple.Tool 替换为 Tool 类的实际内存地址。
2、运行时常量池:直接引用放在运行时常量池->实现在堆,逻辑上属于方法区。
运行时常量池是在类加载完成之后,将 Class 常量池中的符号引用值转存到运行时常量池中,类在解析之后,将符号引用替换成直接引用。 运行时常量池在 JDK1.7 版本之后,就移到堆内存中了,这里指的是物理空间,而逻辑上还是属于方法区(方法区是逻辑分区)。 在 JDK1.8 中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,变动的只是方法 区中内容的物理存放位置,但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放,逻辑上还是属于方法区的。
3、字符串常量池:jvm为了高效的使用String
以 JDK1.8 为例,字符串常量池是存放在堆中,并且与 java.lang.String 类有很大关系。设计这块内存区域的原因在于:String 对象作为 Java 语言中重
要的数据类型,是内存中占据空间最大的一个对象。高效地使用字符串,可以提升系统的整体性能。

String的理解
(1)String 类分析(JDK1.8) String 对象是对 char 数组进行了封装实现的对象,主要有 2 个成员变量:char 数组,hash 值。
String 类被 final 关键字修饰了,而且变量 char 数组也被 final 修饰了。 我们知道类被 final 修饰代表该类不可继承,而 char[]被 final+private 修饰,代表了 String 对象不可被更改。Java 实现的这个特性叫作 String 对象的不 可变性,即 String 对象一旦创建成功,就不能再对它进行改变。
这样处理的好处:
第一,保证String对象的安全性。
第二,保证hash属性值不会频繁变更,确保了唯一性,使得类似hashMap容器 才能实现相应的key-value缓存功能。
第三,可以实现字符串常量池。
在 Java 中,通常有两种创建字符串对象的方式,一种是通过字符串常量的方式创建,如 String str=“abc”;另一种是字 符串变量通过 new 形式的创建,如 String str = new String(“abc”)
(2)string的创建方式

在这里插入图片描述
第一种:String abc = “abc”;//代码编译加载时,会在字符串常量池检查是否已创建”abc”,如果有直接返回常量池中的字符串引用,如果没有则创建常量 “abc”,运行时,返回常量池中的字符串引用。
在这里插入图片描述
第二种:String str = new String(“abc”);//代码编译加载时,会在常量池中创建常量 “abc”;
在这里插入图片描述
第三种:
Location locaton = new Location();
Location.setCity(“深圳”);
Location.setRegion(“南山”);
不会在常量池中创建,直接在堆中创建
第四种:String str = “ab”+”cd”+”ef”;
会生成3个对象,效率最低,编译器会自动优化成abcdef
第五种:String str = “abcdef”;
For(int i = 0;i<1000;i++){
Str = str + i ;
}
编译器也会进行优化为str = (new StringBuilder(String.valueOf(str)).append(i).toString());
第六种:String a = new String(“abc”).intern(); String b = new String(“abc”);
Intern()方法会有两步,第一步会去字符串常量池中检查有没有”abc”,如果有则返回引用,没有就创建返回引用,b就不会再去创建了,intern()方法会直接去常量池中找,找到后 返回引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值