• JVM学习--内存结构


    引言

    程序计数器

    程序计数器(Program Counter Register,寄存器),Java源代码运行的第一步就是将源码编译成JVM指令,每一条指令都有对应的执行地址,JVM通过程序计数器来记住每条指令的下一个执行地址,这样就实现的程序代码的执行。

     

    作用:是记住下一条jvm指令的执行地址

    特点:

    • 线程私有,每一个线程都有自己的一个程序计数器
    • 不会存在线程溢出

     Java虚拟机栈

    定义

    Java虚拟机栈(Java Virtual Machine Stacks)

    • 每个虚拟机运行时需要的内存,成为虚拟机栈。
    • 每个站有多个栈帧(Frame)组成,对应着诶此方法调用时所占用的内存。
    • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
    • 指令-xss是可以调整栈的大小的

    判断方法内的局部变量是否是线程安全的?

    • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的。
    • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。

    栈内存溢出

    1. 栈帧过多引起栈内存溢出,比如递归过程中没有比较好的跳出递归条件,导致无限递归,此时必然会导致栈内存溢出。
    2. 栈帧过大导致栈内存溢出

    线程运行诊断

    1.CPU占用过高时

    • 可以用top命令可以找到那个进程对cpu的占用过高,
    • ps H -eo pid,tid,%cpu | grep 进程id 可以定位到该进程,查询出该进程中哪一个线程CPU占用过高
    • jstack 进程号可以看到该进程的整个线程

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

    jstack命令可以看到死锁信息,代码中第一个线程给对象a加了锁,然后休眠,休眠之后在进行锁住b,但是第二个进程已经先把b锁住了,再打算锁住a,此时需要等待第一个线程解锁a。第一个线程现在又要来锁b,需要等待第二个进程解锁b,此时第一个第二个线程互相等待,进入死锁状态。

    class A{};
    class B{};
    public class Demo1_3 {
        static A a = new A();
        static B b = new B();
    
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                synchronized (a) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (b) {
                        System.out.println("我获得了 a 和 b");
                    }
                }
            }).start();
            Thread.sleep(1000);
            new Thread(()->{
                synchronized (b) {
                    synchronized (a) {
                        System.out.println("我获得了 a 和 b");
                    }
                }
            }).start();
        }
    
    }

     本地方法栈

    先解释一下本地方法,简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。本地方法栈就是起到为本地方法提供内存空间的作用。

    定义

    堆(Heap),通过new关键字来创建,创建对象时都会使用堆内存。

    特点:

    1. 线程共享,堆中对象都需要考虑线程安全问题。
    2. 有垃圾回收机制
    3. -Xmx指令可以调整堆空间大小

    堆内存溢出

    public class Demo1_5 {
    
        public static void main(String[] args) {
            int i = 0;
            try {
                List<String> list = new ArrayList<>();
                String a = "hello";
                while (true) {
                    list.add(a); // hello, hellohello, hellohellohellohello ...
                    a = a + a;  // hellohellohellohello
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println(i);
            }
        }
    }

    堆内存诊断

    jps工具

    • 查看当前系统中有哪些Java进程

    jmap工具

    • 查看堆内存占用情况 jmap -heap 进程id

    jconsole工具

    • 图形化界面,多功能的监测工具,可以连续监测

    方法区

    所有JVM线程共享一个方法区

    存储和类结构相关的信息,包括成员变量、方法数据、方法函数、构造器和一些特殊方法。

    方法区内存溢出

    JDK1.8之前回导致永久代内存溢出

    JDK1.8之后会导致元空间内存溢出

    -XX:MaxMetaspaceSize=8m指令可以设置方法区内存空间,下面代码使用类加载器循环创建一万个类,导致方法区内存溢出。
    public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
        public static void main(String[] args) {
            int j = 0;
            try {
                Demo1_8 test = new Demo1_8();
                for (int i = 0; i < 10000; i++, j++) {
                    // ClassWriter 作用是生成类的二进制字节码
                    ClassWriter cw = new ClassWriter(0);
                    // 版本号, public, 类名, 包名, 父类, 接口
                    cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                    // 返回 byte[]
                    byte[] code = cw.toByteArray();
                    // 执行了类的加载
                    test.defineClass("Class" + i, code, 0, code.length); // Class 对象
                }
            } finally {
                System.out.println(j);
            }
        }
    }

    运行时常量池

    二进制字节码包含:类基本信息、常量池、类方法定义,类方法定义中包括了JVM指令

    javap工具可以对.class文件进行反编译,-c参数可以显示出类的详细信息

    常量池:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。

    运行时常量池:常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变成真实地址。

    直接内存

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

    分配和回收原理:

    1. 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法     
    2.  ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
  • 相关阅读:
    sqlserver日期推算
    bcp sqlserver 导入 导出 数据
    sqlserver 2008 序列号
    mysql 学习总结
    使用Eclipse对JUnit测试函数进行Debug时断点无效问题
    ORACLE死锁故障排查的一般性手法的备忘录
    BigDecimal进行Format时产生的[java.lang.IllegalArgumentException: Digits < 0]异常
    Java中keySet()返回值的排序问题
    严蔚敏数据结构视频教程下载
    C、C++经典书籍
  • 原文地址:https://www.cnblogs.com/s1awwhy/p/13706845.html
Copyright © 2020-2023  润新知