Skip to content

JVM 学习笔记

内存结构

image-20230319164950082

程序计数器

image-20230103191755886

作用:记录下一条JVM指令的执行地址

特点:

  1. 线程私有
  2. 不存在内存溢出

虚拟机栈

  1. 每个线程运行时所需的内存称为虚拟机栈。
  2. 每个栈由多个栈帧(Frame)组成,每个栈帧对应每次方法调用时占用的内存。
  3. 每个线程只能有一个活动栈帧,对应当前正在执行的方法。

问题辨析

  1. 垃圾回收会管理栈内存吗?不会,栈产生的栈帧内存每次调用完成后都会弹出栈,会被自动回收。
  2. 栈内存分配越大越好?不,栈内存大了,线程数会减少。
  3. 方法内局部变量是否线程安全?
  4. 如果变量作用范围只是在方法内,则线程安全。
  5. 若变量引用了对象,则需要考虑线程安全

栈内存溢出

  1. 栈帧过多导致栈内存溢出
  2. 栈帧过大导致栈内存溢出

案例1:CPU占用过多

定位

  • 用top定位哪个进程对CPU的占用过高
  • ps H -eo pid,tid,%cpu | grep 进程id (线程id为10进制)
  • jstack 进程id (线程id为16进制)
    • 通过线程id进一步定位到代码的源码行号

案例2:程序运行较长时间无结果

jstack命令定位查看是否发生死锁

本地方法栈

image-20230104180717068

用native修饰image-20230104185501023

image-20230104185451328

image-20230104185238956

通过new创建的对象都会使用堆内存

特点

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

堆内存溢出

将堆内存设置小些,以方便排查堆内存溢出问题

堆内存诊断

工具

  1. jps
  2. jmap
  3. jconsole
shell
PS C:\Users\28599\Desktop\JVM\代\jvm\jvm> jmap -heap 8284
Attaching to process ID 8284, please wait...
Debugger attached successfully.
Server compiler detected.                          
JVM version is 25.331-b09                          
                                                   
using thread-local object allocation.              
Parallel GC with 8 thread(s)                       
                                                   
Heap Configuration:                                
   MinHeapFreeRatio         = 0                    
   MaxHeapFreeRatio         = 100                  
   MaxHeapSize              = 4250927104 (4054.0MB)
   NewSize                  = 88604672 (84.5MB)    
   MaxNewSize               = 1416626176 (1351.0MB)
   OldSize                  = 177733632 (169.5MB)  
   NewRatio                 = 2                    
   SurvivorRatio            = 8                    
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 66584576 (63.5MB)
   used     = 1331712 (1.27001953125MB)
   free     = 65252864 (62.22998046875MB)
   2.0000307578740157% used
From Space:
   capacity = 11010048 (10.5MB)
   used     = 0 (0.0MB)
   free     = 11010048 (10.5MB)
   0.0% used
To Space:
   capacity = 11010048 (10.5MB)
   used     = 0 (0.0MB)
   free     = 11010048 (10.5MB)
   0.0% used
PS Old Generation
   capacity = 177733632 (169.5MB)
   used     = 1052296 (1.0035476684570312MB)
   free     = 176681336 (168.49645233154297MB)
   0.5920635212135877% used

3162 interned Strings occupying 280536 bytes.

image-20230106125512561

jvisualvm

image-20230106133624840

执行堆dump

image-20230106133645108

查找最大的对象

image-20230106133903389

image-20230106133928146

方法区

image-20230106135030012

存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化和接口初始化中使用的特殊方法。

JDK7 之前(永久代)用于存储已被虚拟机加载的类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。每当一个类初次被加载的时候,它的元数据都会被放到永久代中。永久代大小有 限制,如果加载的类太多,很可能导致永久代内存溢出,即 java.lang.OutOfMemoryError: PermGen。

JDK8 彻底将永久代移除出 HotSpot JVM,将其原有的数据迁移至 Java Heap 或 Native Heap(Metaspace),取代它的是另一个内存区域被称为元空间(Metaspace)。

元空间(Metaspace) :元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常 量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实 现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 可以通过 -XX:MetaspaceSize 和 -XX: MaxMetaspaceSize 配置内存大小。

常量池

shell
javap -v .\HelloWorld.class
shell
Classfile /C:/Users/28599/Desktop/JVM/代码/jvm/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.class
  Last modified 2023-1-3; size 567 bytes
  MD5 checksum 8efebdac91aa496515fa1c161184e354
  Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/itcast/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.itcast.jvm.t5.HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

类加载机制

image-20230319174329006

双亲委派机制

AppClassLoader ---> ExtClassLoader ---> Bootstrap ClassLoader(C++编写,JVM的一部分)

  1. 类加载器会收到类加载的请求
  2. 将这个请求向上委托给父加载器去完成,一直向上委托,直到启动类加载器
  3. 启动类加载器检查是否能加载当前类,能加载就结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载

2

image-20220912223708700

image-20220913095147568

JNI:Java Native Interface (Java本地方法接口)

凡是带了native关键字的方法就会进入本地方法栈;

Native Method Stack 本地方法栈

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java在诞生的时候是 C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了一块区域处理标 记为native的代码,它的具体做法是 在 Native Method Stack 中登记native方法,在 ( Execution Engine ) 执行引擎执行的时候加载Native Libraies。 目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统 管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用 Socket通信,也可以使用Web Service等等

image-20220913131959556

四种垃圾收集器

image-20220913132922142

GC 算法

  1. 引用计数法:每个对象有一个引用计数器,当对象被引用一次则计数器加一,当对象引用失效一次则计数器减一,计数器为0的对象则是垃圾对象,可以被GC回收。(JVM一般不采用这种方法)
  2. 复制算法:将内存划分为大小相等的两块,每次只使用其中的一块,当这一块用完后,再将还存活的对象复制到另一块上,最后将已使用过的内存空间一次清理掉。
  3. 标记-整理算法:当可以使用的内存被耗尽时,GC线程就会将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉。
  4. 标记压缩:让所有存活的对象都向一端移动,然后清理掉端边界以外的内存。
  5. 标记清除压缩:多次清除之后再压缩,从而减少移动对象的成本。