jvm 内存结构

JVM内存结构

在这里插入图片描述

程序计数器(Program Counter Register)
  • 定义:程序计数器是用于存放下一条指令地址的地方。

  • 作用:存放下一条jvm指令的执行地址,如果执行的是 Native 方法,计数器值为Undefined。

  • 特点:

    • 线程私有
    • 不会存在内存溢出(OutOfMemoryError)
Java 虚拟机栈(Java Virtual Machine Stacks)
  • 定义:每个线程运行时所需要的内存,称为虚拟机栈;每个栈由多个栈帧(Frame)组成,一个栈帧对应着一次方法调用时所占用的内存;每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
  • 特点:
    • 线程私有
    • 会抛出StackOverflowError和OutOfMemoryError异常
  • 栈内存溢出问题
    • 栈帧过多导致栈内存溢出(递归调用没有设置结束条件)
    • 栈帧过大导致栈内存溢出

问题辨析

1、垃圾回收是否涉及栈内存?

垃圾回收一般作用对象是堆空间,虚拟机栈一般栈帧执行结束后就弹栈释放内存了,所以不涉及垃圾回收。

2、栈内存分配越大越好吗?

不是的,每个栈对应一个线程,如果栈内存分配过大,势必导致能创建的线程数量就少了,对应用程序的性能也会有影响。 虚拟机默认的栈大小是1MB(-Xss1024k)。

3、方法内的局部变量是否线程安全?

  • 如果方法内部局部变量没有逃离方法的作用范围,那么它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围(比如引用对象被方法返回,被其它线程引用到了),那么就需要考虑线程安全问题

线程运行诊断

案例1:CPU 占用过多怎么排查问题?

  • 第一步:linux 中使用top命令定位哪个进程对CPU的占用过高

在这里插入图片描述

  • 第二步:用ps命令进一步定位是哪个线程引起的CPU占用过高(ps H -eo pid,tid,%cpu | grep pid)
 PID   TID  %CPU
13086 13750  0.0
13086 13751  0.0
13086 13760  1.5
13086 13761  0.0
13086 13762  0.0
13086 13763  1.5
13086 13764  0.0
13086 13765  0.0
13086 13766  1.5
13086 13767  0.0
13086 13768  0.0
  • 第三步:jstack pid查看进程中每个线程的情况,将CPU占用高的线程id转成16进制,与线程详情中的nid匹配,查看具体是哪个线程
"pool-2-thread-1" #50 prio=5 os_prio=0 tid=0x00007f9c24e5a000 nid=0x34a8 waiting on condition [0x00007f9c176f5000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000dda93d10> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)

案例2:程序运行很长时间没有结果怎么回事?

查找方法同上。

本地方法栈(Native Method Stack)
  • 定义:本地方法栈是为执行native方法提供内存空间,本地方法的实现是由C语言编写的,而非Java。
  • 特点:
    • 线程私有
    • 会抛出StackOverflowError和OutOfMemoryError异常
堆(Heap)
  • 定义:通过关键字new创建的对象都会存放在堆内存中

  • 特点:

    • 线程共享,堆中的对象都要考虑线程安全问题
    • 有垃圾回收机制
  • 堆内存溢出

    • 当堆中的对象不断创建,同时新创建的对象一直被引用,这时候堆中所有对象占用的内存超过堆内存大小,会出现内存溢出情况
    • 堆内存大小设置(-Xmx1024m)
  • 堆内存诊断

    • jps 工具:查看当前系统中有哪些java进程(jps命令查看进程)
    D:\code\exercise\out\production\exercise\com\ityang\jvm>jps
    12960 KotlinCompileDaemon
    6576 Jps
    13768 Jae
    12268
    13484 Launcher
    5852 Launcher
    
    • jmap 工具:查看堆内存占用情况(某一时刻)
    D:\code\exercise\out\production\exercise\com\ityang\jvm>jmap -heap 13768
    Attaching to process ID 13768, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.181-b13
    
    using thread-local object allocation.
    Parallel GC with 4 thread(s)
    
    Heap Configuration:
       MinHeapFreeRatio         = 0
       MaxHeapFreeRatio         = 100
       MaxHeapSize              = 4269801472 (4072.0MB)
       NewSize                  = 89128960 (85.0MB)
       MaxNewSize               = 1422917632 (1357.0MB)
       OldSize                  = 179306496 (171.0MB)
       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 = 67108864 (64.0MB)
       used     = 6716456 (6.405311584472656MB)
       free     = 60392408 (57.594688415527344MB)
       10.008299350738525% 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 = 179306496 (171.0MB)
       used     = 0 (0.0MB)
       free     = 179306496 (171.0MB)
       0.0% used
    
    3177 interned Strings occupying 260408 bytes.
    
    • jconsole 工具:图形化界面,多功能的监测工具,可以连续监测。(输入jconsole命令会跳出以下图形化界面)

在这里插入图片描述

案例运行诊断

问题:垃圾回收后,内存占用仍然很高?

使用jvisualvm查看堆内存情况,可以进行堆Dump,查看具体哪些类占用堆内存大

在这里插入图片描述

方法区(Method Area)
  • 定义:方法区中存储一些类相关的信息,比如:类加载器、运行时常量池、类的成员变量、成员方法、构造器方法等。方法区在虚拟机启动时被创建,逻辑上是堆的组成部分,不同的实现对方法区内存的定义也不一样,比如JDK1.8以前的HotSpot JVM有方法区,也叫永久代(permanent generation),1.8之后用元空间替换方法区,不同的是方法区使用的是jvm内存,而元空间使用计算机本地内存。jdk 1.7之后,原先位于方法区里的字符串常量池已被移动到了java堆中,所以说方法区其实只是一种JVM的规范。永久代和元空间都是方法区的具体实现。

  • 特点:

    • 所有线程共享
    • 会出现内存溢出
  • 常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息

    Classfile /D:/code/exercise/out/production/exercise/com/ityang/view/Demo01.class
      Last modified 2021-4-23; size 556 bytes
      MD5 checksum d4f4b70b966ce9536607539038f768f1
      Compiled from "Demo01.java"
    public class com.ityang.view.Demo01
      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            // com/ityang/view/Demo01
       #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               Lcom/ityang/view/Demo01;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               SourceFile
      #19 = Utf8               Demo01.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               com/ityang/view/Demo01
      #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 com.ityang.view.Demo01();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/ityang/view/Demo01;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          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 7: 0
            line 8: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
    }
    SourceFile: "Demo01.java"
    
    • 运行时常量池:常量池是*.class 文件中的,当该类被加载进内存,它的常量池信息就会放入运行时常量池(内存中),并把里面的符号地址变为真实的引用地址
  • 字符串常量池(StringTable):HashTable结构,不能扩容;JDK1.6时,StringTable在常量池中,JDK1.8在堆空间中

    • 常量池中的字符串仅是符号,第一次用到时才变成对象(懒加载)
    • 利用字符串常量池的机制可以避免重复创建对象
    • 字符串变量拼接的原理是 StringBuild.apend(),最后toString(),最后生成的对象是在堆中的
    • 字符串常量拼接的原理是编译器优化,编译器就已经拼接好,所以运行时直接去常量池中找拼接好的对象
    • 可以使用intern方法,主动将字符串常量池中还没有的字符串对象放入字符串常量池中
      • jdk1.8 将这个字符串对象尝试放入字符串常量池中,如果有则不放入,没有就把此字符串放入池中,最后把池中的这个对象返回
      • jdk1.6 将这个字符串对象尝试放入字符串常量池中,如果有则不放入,没有就把此字符串复制一份放入池中,最后把池中的对象返回
  • StringTable 垃圾回收:如果堆内存紧张触发垃圾回收的时候会把字符串常量池中没有被引用的字符串回收掉

  • StringTable 性能调优:

    • 调用-XX:StringTableSize=桶个数的数量
    • 考虑字符串对象是否入字符串常量池
  • 内存溢出:动态代理动态加载类的时候,使用不当会导致元空间的内存溢出

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

StringTable面试题

public class Demo01 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab"; // 去字符串常量池中找"ab",没有就创建一个
        String s4 = s1+s2; // new StringBuilder().append("a").append("b").toString();  new String("ab")
        System.out.println(s3 == s4); // false 因为s3是字符串常量池中的ab,s4是堆中的ab
        String s5 = "a" + "b";  // 直接去字符串常量池中找"ab",这是javac在编译期的优化,结果已经在编译期被确定ab
        System.out.println(s3 == s5); // true

        String s = new String("a") + new String("b"); // ab  堆中的对象
        String s6 = s.intern();  // jdk1.8 将这个字符串对象尝试放入字符串常量池中,如果有则不放入,没有就放入,最后把池中的对象返回
        System.out.println(s3 == s6); // true
        System.out.println(s == s3); // false 如果将 String s3 = "ab"; 这行代码移动到s6后面,结果就为true,就看谁先放入池中

        // 如果是jdk1.6,有什么区别?
    }
}
直接内存
  • 定义:直接内存区域并不是JVM运行时数据区的一部分,但它却会被频繁的使用,原因是NIO这个包。NIO(New input/output)是JDK1.4中新加入的类,引入了一种基于通道(channel)和缓冲区(buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过堆上的DirectByteBuffer对象对这块内存进行引用和操作。
  • 特点:
    • 分配回收成本较高,但是读写性能也很高
    • 不受JVM内存回收管理
    • 当直接内存和JVM内存占用加起来超过系统内存的时候就会出现溢出
  • 直接内存释放:需要手动调用unsafe类的freeMemory方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值