Big-Edian 存储和 Little-Edian 存储
在 JVM 虚拟机规范中有对 class 字节内容的顺序的一句话,多字节数据项总是按照 Big-Endian 的顺序进行存储
,刚开始不太明白,只是根据规范解析了一下,具体的java 代码:
1 |
|
在 JVM 虚拟机规范中有对 class 字节内容的顺序的一句话,多字节数据项总是按照 Big-Endian 的顺序进行存储
,刚开始不太明白,只是根据规范解析了一下,具体的java 代码:
1 |
|
最近又碰到的 oom 的问题,一直在尝试定位中,由于现实使用的 G1 的垃圾回收器。所以今天打算线上的排查历程和方案查询出来。
1 | -Xmx1024m 最大堆内存 |
在排查的过程中用到的下面几个命令
1 | jmap |
出现 OOM 的问题,一般情况下来说,都是堆上面内存分配的太多,且无法回收,导致 JVM 的内存溢出。
jps
查看 java 运行时的 pidjmap -heap pid
看下堆上各个区的占用的内存大小jmap -histo pid
可以查看对应的类型的大小,或者使用 dump 成一个文件进行分析
在对堆上的类型对象进行分析的时候,发现堆上的内存大小和回收的基本正常,实际使用的内存是大于堆上的内存, 这个时候我就开始怀疑是堆外内存的泄露的问题。
使用 pmap -x pid | sort -n -k3
指令,看下占用内存的地址空间和大小
perf
命令,基本就两条语句perf record -g -p pid
开启监控栈函数调用。运行一段时间后 Ctrl+C 结束,会生成一个文件 perf.data。执行perf report -i perf.data
查看报告。
根据查看的内容,定位到 zip 的内容,但是内容还是不大,也 review zip 相关代码,进行本地测试,均为发生 OOM 的现象。
前面的几个办法都无法定位问题,只能使用最笨的办法,打印直接内存的大小。具体的代码如下:
public BufferPoolMXBean getDirectBufferPoolMBean(){
return ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class)
.stream()
.filter(e -> e.getName().equals("direct"))
.findFirst()
.orElseThrow(null);
}
public JavaNioAccess.BufferPool getNioBufferPool(){
return SharedSecrets.getJavaNioAccess().getDirectBufferPool();
}
/**
* -XX:MaxDirectMemorySize=60M
*/
@Test
public void testGetMaxDirectMemory(){
ByteBuffer.allocateDirect(25*1024*1024);
System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024.0);
System.out.println(VM.maxDirectMemory() / 1024.0 / 1024.0);
System.out.println(getDirectBufferPoolMBean().getTotalCapacity() / 1024.0 / 1024.0);
System.out.println(getNioBufferPool().getTotalCapacity() / 1024.0 / 1024.0);
}
模拟JVM的运行,需要有两个个方面的知识准备
对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:
重点说下方法区,方法区有两个实现
找到 Method 的 DescriptionIndex 的属性,找到对应的描述,例如:
1 | public class AddMain { |
这个例子中的 java 代码,add 方法对应的代码是 (II)I,
最后一个 I 代表返回值,这个代表两个整型的参数.
1 | private static int add(int a, int b,String c,boolean d) { |
同样,(IILjava/lang/String;Z)I 代表有4个参数,字符串的表示是:Ljava/lang/String;
,解析比较特殊。
在运行一段 java 代码的时候需要经过编译
,验证
,加载
和运行
,具体如下图:
对于 Java 源码变成字节码的编译过程,我们暂且跳过不讨论。
想弄清楚 java 代码的运行原理,其实本质就是 java 字节码如何被 jvm 执行。
下面我们就从两个方面去认识字节码:
我们先写一个简单的 java 程序:
1 |
|
在学习的JVM的时候,最重要的是认识JVM的指令,JVM指令很多,为了方便记忆,可以根据前缀和功能进行分类:
例如:nop
指令代表是一个空指令,JVM收到指令后,什么都不用做,等待下一个指令。
const
的范围从0x01—0x0f,负责把数据推送到栈顶。例如:iconst_0负责吧整型的0推送到栈顶。 fconst_0负责把float的0推送到栈顶。const可以分为以下几种类型:
iconst_
:把int推送栈顶fconst_
:推送float类型栈顶lconst_
:推送long到栈顶dconst_
:提送double至栈顶
上述的为简写的前缀,后续可以跟参数,例如:iconst_0,iconst_1
分别代表推送0和1至栈顶。