zoukankan      html  css  js  c++  java
  • 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 来释放直接内存
  • 相关阅读:
    zoj 1239 Hanoi Tower Troubles Again!
    zoj 1221 Risk
    uva 10192 Vacation
    uva 10066 The Twin Towers
    uva 531 Compromise
    uva 103 Stacking Boxes
    稳定婚姻模型
    Ants UVA
    Golden Tiger Claw UVA
    关于upper、lower bound 的探讨
  • 原文地址:https://www.cnblogs.com/s1awwhy/p/13706845.html
Copyright © 2011-2022 走看看