初识JVM

JVM探究

1. JVM的位置

2. JVM体系结构

3. 类加载器

​ 作用:加载Class文件~

bootstrap class loader 根加载器

extension class loader 扩展类加载器

app class loader 系统类加载器

自定义加载器 继承classloader

他们都继承classloader这个抽象类

4. 双亲委派机制:安全

APP----->Exc-----Boot(最终执行)

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

5. 沙箱安全机制

6. Native

凡是带了native关键字的,说明java的作用范围达不到了,回去调用C语言的库

会进入本地方法栈,调用本地方法接口 java native interface JNI 调用本地方法库

JNI 的作用:扩展Java的只用,融合不同的编程语言为Java所用

它在内存区域中开辟了一块标记区域, native Method stack,登记native 方法

最终执行的时候,加载本地方法库中的方法 通过JNI

7. PC寄存器

程序计数器

每一个线程都用一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码

例如 汇编语言中的 CS:IP

8. 方法区

方法区是被所有线程共享的,所有的字段,方法,字节码。以及一些特殊的方法,如构造函数,接口代码也在次定义,简单来说,所有定义的方法信息都保存在该区域,此区域属于共享区间

静态变量,常量,类信息(构造函数,接口定义),运行时常量池也存在方法区中,但是实例变量存在堆内存中,和方法区无关

static

final

Class

常量池

9. 栈 数据结构

参考汇编 Stack 结构 SS:SP

栈满 就Stack over flow error

正常递归调用的时候 最容易栈溢出

public class HelloLoader {

    public static void main(String[] args) {
        System.out.println("please loader me ,thank you");

        HelloLoader helloLoader = new HelloLoader();

        helloLoader.a();
    }

    public void a(){
        b();
    }


    public void b(){
        a();
    }
}

吃多了吐就是栈,吃多了拉就是队列

线程结束栈内存也就得到了释放,不存在垃圾回收的问题

栈存储:八大基本类型

​ 对象引用

​ 示例的方法

10. 堆

Heap,一个JVM只有一个堆内存 堆内存的大小是可以条件的

类加载器读取类文件后,一般把什么放入堆中?

​ 类、

​ 方法

​ 常量

​ 变量

保存我们所有引用类型的真实对象

堆内存要细分3个区域

  • 新生区 eden young
  • 养老区 old
  • 永久区 perm

GC垃圾回收 主要是在eden和养老区

堆内存不够 就会报OOM

import java.util.Random;

public class OOM {
    public static void main(String[] args) {
        String str = "immortal";

        while (true){
            str += str + new Random().nextInt(1_1111_1111)+ new Random().nextInt(1_1111_1111);
        }
    }
}

在jdk8以后,永久存储区 改了个名字叫元空间

新生区

  • 类:诞生,成长,死亡的地方
  • 伊甸园区 所有的对象都是在伊甸园区new出来的
  • 幸存者区
    • from
    • to

真理:经过研究,99%的对象都是临时对象!

老年区

永久区

这个区域常驻内存的,用来存放JDK自身的CLass对象 java运行时的一些环境

一个启动类,加载了大量的第三方Jar包,Tomcat部署了太多应用,大量动态生成反射类,不断的被加载,知道内存满 就会OOM

jdk1.6 : 永久代 常量池在方法区

jdk1.7 :永久代 慢慢退化了 去永久代 常量池在堆中

jdk1.8之后 无永久带 常量池在元空间中

逻辑上存在,物理上不存在

上代码

public class Perm {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();  //byte单位
        //返回Jvm初始化总内存
        long totalMemory = Runtime.getRuntime().totalMemory();
        /**
         * 默认情况下,分配的总内存是电脑内存的四分之一
         *               初始化内存是六十四分之一
         *配置堆的大小
         * -Xms1024m -Xmx1024m -XX:+PrintGCDetails
         */

        /**
         * OOM:
         *      尝试扩大内存查看结果
         *      分析内存,看下哪里出现了问题
         */
        System.out.println("MAX:" + max + " byte  " + (max/(double)1024/1024) + "MB" );

        System.out.println("totalMemory:" + totalMemory + " byte  " + (totalMemory/(double)1024/1024) + "MB" );
    }
}

(699392 + 305664) / 1024 = 981.5M

11. 内存快照jprofile

分析dump文件,快速定位内存泄漏

获得堆中的对象

import java.util.ArrayList;

/**
 * dump:
 *      -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
 *		系统出现oom的时候会Heap的信息Dump下来
 */
public class JProfile {

    byte[] array = new byte[1*1024*1024];

    public static void main(String[] args) {
        ArrayList<JProfile> arrayList = new ArrayList<JProfile>();
        int count = 0;
        try {
            while (true){
                arrayList.add(new JProfile());
                count++;
            }
        } catch (OutOfMemoryError o) {
            o.printStackTrace();
        }

    }
}

12. GC

JVM在GC的时,并不是对这三个区域统一回收,大部分的时候都是在新生代

  • 新生代
  • 幸存区(from,to)
  • 老年区

GC的两种类型

  • 轻GC
  • Full GC

GC的常用算法

引用计数器

  • 计数器本身也会有消耗,每个对象都会有自己的一块空间存放计数

复制算法(重点)

  • 谁空谁是to
  • 每次GC都会将Eden园区活这的对象移发到幸存区中,一旦Eden区被GC后就会是空的
  • 默认当一个对象经历15次GC都还没有死就会进入养老区 这个参数可以自定义

标记清除算法

缺点:

​ 两次扫描,严重浪费时间,会产生内存碎片

优点:

​ 不会需要额外的空间

标记压缩算法

  • 对标记清楚算法的优化

13. 总结

内存效率 :复制算法----->标签清除------>标记压缩 (时间复杂度)

内存整齐度 :复制算法==标记压缩------>标签清除

内存利用率 :标记压缩==标签清除------>复制算法

GC :分代收集算法

年轻代: 存活率低, 复制算法 效率最高

老年代: 存活率高 标签清除 标记压缩 混合实现 ,大型项目慢慢调

posted @ 2020-12-24 23:30  immortal_mode  阅读(91)  评论(0)    收藏  举报