【JVM笔记】JVM内存结构

【JVM笔记】JVM内存结构

JVM内存结构图

1. 程序计数器(Program Counter Register)

物理上,用寄存器实现

1.1 定义

一块较小的内存空间,可以当成当前线程所执行字节码的行号指示器。

1.2 作用

二进制字节码(jvm指令)通过解释器变成机器码,再由CPU执行

存放下一条jvm指令的执行地址

1.3 特点

  • 线程私有
  • 唯一一个不存在内存溢出的区域

2. 虚拟机栈

栈:一种先进后出的数据结构

2.1 定义

  • 每个线程运行时需要的内存空间,称为虚拟机栈
  • 每个栈由栈帧(拥有局部变量表、操作数栈、动态链接、方法出口信息)组成,对应着每次方法调用时所占用的内存。
  • 每个线程只能有一个活动栈帧(位于栈顶),对应当前正在执行的方法

2.2 栈内存溢出 StackOverFlowError

-Xss256k 设置栈内存大小

  • 栈帧过多:方法递归调用没有终止条件
  • 栈帧过大

2.3 线程运行诊断

case 1: cpu占用过高
定位

  • top定位占用cpu过高的进程
  • ps H -eo pid,tid,%cpu | grep 进程id 定位进程中占用cpu过高的线程
  • jstack 进程id,查找转换成16进制后的线程id,进一步定位到问题代码行数

case 2: 程序运行很长时间没有结果

  • jstack 进程id 查看底部打印信息,是否有死锁等信息。

3. 本地方法栈

调用本地方法(一般为C/C++方法)时提供的内存空间

4. 堆

JVM管理的最大一块内存,线程共享。

4.1 定义

存放对象实例,几乎所有对象实例以及数组都在这里分配内存

特点

  • 线程共享,堆中对象需考虑线程安全问题
  • 有垃圾回收机制

4.2 堆内存溢出

-Xmx8M 修改最大堆内存

// java.lang.OutOfMemoryError: Java heap space
void testOOM() {
        List<String> list = new ArrayList<>();
        while (true) {
            list.add("hello");
        }
    }

4.3 堆内存诊断

工具

  1. jps工具
    • 查看当前系统中有哪些java进程
  2. jmap工具
    • 查看堆内存占用情况 jmap -heap 进程id
  3. jconsole工具
    • 图形界面,多功能监测工具,可以连续监测

案例:垃圾回收后,内存占用仍然很高

  • jvisualvm工具
  • 堆dump
  • 查找较大的对象

5. 方法区

JDK8 JVM规范-方法区

5.1 定义

  • 与堆一样,线程共享。
  • 存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
  • 逻辑上属于堆的一部分,具体JVM实现不一定

方法区与永久代,元空间的关系

方法区是一种规范,永久代或元空间是它的一种实现。(就类似接口与实现了该接口的类那样)

Orcale公司的HotSpot虚拟机

  • JDK8以前,永久代实现了方法区,它在堆中
  • JDK8以后,移除永久代,元空间实现了方法区,用的是操作系统的直接内存

5.2 组成

5.3 内存溢出

  • JDK8以前导致永久代内存溢出
java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m 设置最大永久代大小
  • JDK8以后导致元空间内存溢出
-XX:MaxMetaspaceSize=8m 设置最大元空间大小
java.lang.OutOfMemoryError: Metaspace

场景 (动态加载类 cglib)

  • spring
  • mybatis

5.4 运行时常量池

  • 常量池,就是一张表,虚拟机指令根据此表找到要执行的类目、方法名、参数类型、字面量等信息
  • 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号地址变为真实地址

二进制字节码组成

  • 类基本信息
  • 常量池
  • 类方法定义(包含了虚拟机指令)

javap -v HelloWorld 反编译字节码文件

5.5 串池 StringTable

hashtable结构,不能扩容

// StringDemo.java
public class StringDemo {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
    }

反编译上述文件的字节码文件javap -v StringDemo,得到如下片段(只取其中一部分)

Constant pool:
   #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
   #2 = String             #25            // a
   #3 = String             #26            // b
   #4 = String             #27            // ab
   #5 = Class              #28            // Solution
   #6 = Class              #29            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LSolution;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               s1
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               s2
  #21 = Utf8               s3
  #22 = Utf8               SourceFile
  #23 = Utf8               Solution.java
  #24 = NameAndType        #7:#8          // "<init>":()V
  #25 = Utf8               a
  #26 = Utf8               b
  #27 = Utf8               ab
  #28 = Utf8               Solution
  #29 = Utf8               java/lang/Object


 Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 6
        line 6: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;

常量池与串池的关系

  • 常量池中的信息,都会被加载到运行时常量池中,此时a b ab 只是常量池中的符号,没有变为java字符串对象
  • 指令 ldc #2 在串池中查找"a"对象,没有则把符号a变成"a"对象放入串池中;如果已存在,则使用串池中的对象。

特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池机制,避免创建重复字符串
  • 字符串变量拼接原理是StringBuilder (JDK8)
  • 字符串常量拼接原理是编译器优化
  • 使用intern方法,主动将串池中还没有的字符串对象放入串池
    • JDK8 将这个字符串对象尝试放入串池,如果有则不放入,返回串池中的对象;如果没有则放入串池,并返回这个字符串对象的引用。
    • JDK6 将这个字符串对象尝试放入串池,如果有则不放入,返回串池中的对象;如果没有则复制这个对象一份放入,并返回串池中的对象。

调优

  • -XX:StringTableSize=桶个数
  • 如果有大量字符串,可以考虑入池,节约堆内存空间

6. 直接内存

  • 常见于NIO操作时,用于数据缓冲区
  • 分配回收成本较高,读写性能高
  • 不受JVM内存回收管理

内存溢出
java.lang.OutOfMemoeryError: Direct buffer memory

6.1 分配和回收原理

  • 使用了Unsafe对象完成直接内存的分配回收,回收需要主动调用freeMemory方法
  • ByteBuffer的实现类内部,使用了Cleaner虚引用来检测ByteBuffer对象,一旦它被垃圾回收,就会由ReferenceHandler线程通过Cleanerclean方法调用freeMemory来释放直接内存

禁用显示垃圾回收
-XX: +DisableExplicitGC

# java  jvm 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×