JVM系列文章目录
继续深入JVM内存区域
前言
注:本文基于JDK1.8,是博主个人的JVM学习记录,欢迎各位指正错误的地方
一、深入JVM内存区域
1.JHSDB工具
JHSDB 是一款基于服务性代理实现的进程外调试工具。服务性代理是 HotSpot 虚拟机中一组用于映射 Java 虚拟机运行信息的,主要基于 Java 语言实现的 API集合。它可以用来查看包括诸如:GC回收,分代信息等。具体如何使用各位可以自行百度,这里先不做介绍了。
2.JVM内存处理全流程
-
JVM向操作系统申请内存区域:通过你设置好的参数先操作系统申请内存;
-
初始化运行时数据区:JVM自己获得内存空间后,根据你的指示(根据配置参数),给下面的小弟分配资源(分配堆、栈以及方法区的内存大小);
-
类加载:这里主要是把 class 放入方法区、还有 class 中的静态变量和常量也要放入方法区(后面的博客会细聊);
-
执行方法及 创建对象:启用一个线程,执行main方法,创建对象A1,A2,并将A1,A2的引用放在栈中
JVM优化
在搞清楚上面的JVM的内存处理流程之后,我们结合JHSDB就可以了解以下栈的优化——栈帧之间数据共享(一般情况下,两个不同的栈帧的内存区域是独立的,但是大部分JVM都会做一些优化,是的两个栈有一小部分重叠,这主要体现在方法之间的调用传惨上)。
从字面上可以看出来就是各个栈共享某些数据下面我以一段代码为例来解释以下这个
public class Test {
public void a(int n){
int num = n + 9;
try {
Thread.currentThread().sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num);
}
public static void main(String[] args) {
Test test = new Test();
test.a(1);
}
}
在上面这个方法中我们可以看到mian方法调用了a方法并传递了一个int数值1;
那么在JVM中的现象是这样的,mian方法对应的栈帧中的操作数栈中存入了一个1,然后进入到b方法,a方法的栈帧进入虚拟机栈;a方法变量x(传入的变量1)需要存入到局部变量中,;此时我们可以思考一下,mian的操作数栈中有一个1,a的局部变量中也有一个1,如果在计算机实际内存中使用两块内存来分别存储这个1是不是就有点浪费了,所以JVM中就让它们指向同一个实际内存地址。
大概是这么个样子的图:
用JHSDB来内存信息如下:红色框是它们的共享内存000000
二、内存溢出
做Java开发的话可能很多人都听说这个词,但是大部分人可能都没有详细了解过解决过这种情况
内存溢出有以下几种情况:
栈溢出
栈指的就是虚拟机栈,虚拟机栈大小是设定好的,当虚拟机栈不停的入栈但是不出栈,当它就撑爆了,比如说递归调用方法死循环。可以通过-XssSize 如-Xss 2M
来扩大栈内存,但是在此之前请检查代码查明撑爆的原因。
堆溢出
JVM中堆主要存储堆是对象,这一块是最容易出问题,调优大多数也是调堆,如果这块溢出了,快检查你的对象是不是长生不老了。
可以通过-Xms,-Xmx 参数
来调大小。
方法区溢出
方法区溢出一般是这两种情况:
(1) 运行时常量池溢出
(2)方法区中保存的 Class 对象没有被及时回收掉或者 Class 信息占用的内存超过了我们配置。
直接内存溢出
直接内存溢出这个我没遇到过,就抄一下理论知识了:由直接内存导致的内存溢出,一个比较明显的特征是在 HeapDump 文件中不会看见有什么明显的异常情况,如果发生了 OOM, 同时 Dump 文件很小,可 以考虑重点排查下直接内存方面的原因。
三、常量池
静态常量池
静态常量池指的是class文件里的常量池
运行时常量池
包括字面量(八大基本类型),运行时的方法和字段的引用(官方描述是这样的:运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式,它包括了若干种不同的常量: 从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。)
字符串常量池
这个东西比较有争议,在官方文档中没有明确的定义。但它的确实际存在,在JVM运行的时候的确会有给String类型常量单独分一块区域去存储,并专门优化。下面来针对String来分析。
String
String源码:
从这里其实可以看出String对象的不可变,那么String对象就相对稳定,hash指也就能确定唯一,这样就方便字符串常量池的实现。
String的创建方式
下面描述几种String的创建不同内存情况
-
String a = "abc"
这种情况会在常量池中创建abc
-
String a = new String("abc")
这种情况会在字符串常量池中创建abc,在堆中间创建a对象,并且将a指向常量池中的abc。
-
String a= "ab"+ "cd"+ "ef";
这个在大部分JVM中,在编译阶段会进行优化,变成 abcdef,我们可以反编译class文件来查看,基本上可以看到这样的String a= "abcdef";
-
String a = "abc"; for (int i = 0; i < 10000;i++){ a = a + i; }
经过编译后会优化成这种的
String a = "abc"; for (int i = 0; i < 10000;i++){ a = new StringBuilder(String.valueOf(a)).append(i).toString(); }
-
String a = new String("abc").intern(); String b = new String("abc").intern(); if (a == b){ System.out.println("a=b"); }else { System.out.println("a!=b"); }
这种情况intren()方法会判断字符串常量池中是不是有abc这个变量,有就引用,无就创建。
所以输出结果是a=b。
上一篇:初识JVM
下一篇:玩转JVM对象和引用