Big-Edian 存储和 Little-Edian 存储

在 JVM 虚拟机规范中有对 class 字节内容的顺序的一句话,多字节数据项总是按照 Big-Endian 的顺序进行存储,刚开始不太明白,只是根据规范解析了一下,具体的java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13

public ClassReadCursor(String filePath, ClassParseInfo classParseInfo) {
try {
this.classParseInfo = classParseInfo;
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
this.dataInputStream = new DataInputStream(new ByteArrayInputStream(byteBuffer.array()));
} catch (IOException e) {
e.printStackTrace();
}

}

阅读更多

简述 G1 垃圾回收器和 OOM 问题的排查

最近又碰到的 oom 的问题,一直在尝试定位中,由于现实使用的 G1 的垃圾回收器。所以今天打算线上的排查历程和方案查询出来。

jvm 常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-Xmx1024m 最大堆内存
-Xms1024m 最小堆内存
-Xss256k 设置栈的大小。栈都是每个线程独有一个,所有一般都是几百k的大小。
-XX:MetaspaceSize=128m 元空间的大小
-XX:MaxMetaspaceSize=256m 最大元空间大小
-XX:MaxGCPauseMillis=200 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值
-XX:+UseG1GC 使用 G1 垃圾回收器
-XX:-OmitStackTraceInFastThrow 当一些异常在代码里某个特定位置被抛出很多次的话,HotSpot Server Compiler(C2)会用fast throw来优化这个抛出异常的地方。
-XX:MinHeapFreeRatio=30
-XX:MaxHeapFreeRatio=50
-XX:MaxDirectMemorySize=100M 直接内存大小
-XX:+PrintGCDetails 打印 GC 详细信息
-XX:+DisableExplicitGC 禁止显示GC

几个命令

在排查的过程中用到的下面几个命令

1
2
3
4
jmap
pmap 命令
perf 命令
内存 RSS、VSZ的区别

出现 OOM 的问题,一般情况下来说,都是堆上面内存分配的太多,且无法回收,导致 JVM 的内存溢出。

  1. jps 查看 java 运行时的 pid
  2. jmap -heap pid 看下堆上各个区的占用的内存大小
  3. jmap -histo pid 可以查看对应的类型的大小,或者使用 dump 成一个文件进行分析

    在对堆上的类型对象进行分析的时候,发现堆上的内存大小和回收的基本正常,实际使用的内存是大于堆上的内存, 这个时候我就开始怀疑是堆外内存的泄露的问题。

  4. 使用 pmap -x pid | sort -n -k3 指令,看下占用内存的地址空间和大小

  5. 前面的工具如果再无法定位问题的话,就只能使用 perf 命令,基本就两条语句
  6. perf record -g -p pid 开启监控栈函数调用。运行一段时间后 Ctrl+C 结束,会生成一个文件 perf.data。
  7. 执行perf report -i perf.data查看报告。

    根据查看的内容,定位到 zip 的内容,但是内容还是不大,也 review zip 相关代码,进行本地测试,均为发生 OOM 的现象。

  8. 前面的几个办法都无法定位问题,只能使用最笨的办法,打印直接内存的大小。具体的代码如下:

         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);
         }
    

G1 回收器的特点

阅读更多

手写一个简单的JVM--02.模拟运行JVM

模拟JVM指令的运行

模拟JVM的运行,需要有两个个方面的知识准备

  1. JVM的结构
  2. 字节码的含义
JVM的结构

对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:

JVM

方法区

重点说下方法区,方法区有两个实现

  1. 永久代(1.7以前),永久代分配到堆上
  2. 元空间(1.8),元空间在直接内存上面(分配的快,回收的慢,元空间加载后不会被回收)
阅读更多

字节码判断方法参数的个数

Jvm 如何确定方法的参数的个数

找到 Method 的 DescriptionIndex 的属性,找到对应的描述,例如:

1
2
3
4
5
6
7
8
9
10
11
public class AddMain {
public static void main(String[] args) {

int c = add(100,200);
System.out.println(c);
}

private static int add(int a, int b) {
return a + b;
}
}

这个例子中的 java 代码,add 方法对应的代码是 (II)I,最后一个 I 代表返回值,这个代表两个整型的参数.

1
2
3
private static int add(int a, int b,String c,boolean d) {
return a + b;
}

​ 同样,(IILjava/lang/String;Z)I 代表有4个参数,字符串的表示是:Ljava/lang/String;,解析比较特殊。

阅读更多

手写一个简单的JVM--01. 解析Class文件

java的运行过程

在运行一段 java 代码的时候需要经过编译,验证,加载运行,具体如下图:

运行过程

对于 Java 源码变成字节码的编译过程,我们暂且跳过不讨论。

想弄清楚 java 代码的运行原理,其实本质就是 java 字节码如何被 jvm 执行。

下面我们就从两个方面去认识字节码:

  1. 字节码是什么?
  2. 字节码如何被执行的?

我们先写一个简单的 java 程序:

1
2
3
4
5
6
7
8
9
10

public class AddMain {
public static void main(String[] args) {
int a=1;
int b=2;
int c=a+b;
System.out.println(c);
}
}

阅读更多

JVM指令的速记

在学习的JVM的时候,最重要的是认识JVM的指令,JVM指令很多,为了方便记忆,可以根据前缀和功能进行分类:

例如:nop指令代表是一个空指令,JVM收到指令后,什么都不用做,等待下一个指令。


const把数据推至栈顶

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至栈顶。


push常量到栈顶

阅读更多