JVM入门

 

 JVM偏理论,主要靠背 面试题


JVM的位置

  JVM在操作系统之上,和其他的应用软件层级并列,在之上可以跑java程序

  .java => class文件 => 类加载器 Class Loader =>运行时数据区

 

 

JVM 架构图

 

 

 

 

 

类加载器 ClassLoader 

 

类加载器细分:

  1. bootStrap - 根  类加载器(jre/ lib/ rt.jar)

  2. ext - 扩展加载器 (jre/ lib/ ext/ ...)

  3. app - 应用程序(系统类) 加载器,最常用

 

 双亲委派机制

 选择类加载器的顺序:

  bootStrap->ext->app->custom。先找boot,再找ext、app,最后找costom也就是自己写的。都找不到就报错Class Not Found

好处:

  防止用户乱定义类:比如自定义一个String类,这时会先从BootStrap找,所以不会轮到自定义的String类

 

 

沙箱安全机制

  Java安全模型的核心是 沙箱 sandbox

 

 

组成沙箱的基本组件:

  字节码校验器(bytecode verifier): 确保遵循java语言规范,帮助实现内存保护;核心类不参与校验;

  类加载器:双亲委派机制 + 将代码归入保护域

 

Native

例子:

private native void start0();    //java的范围达不到了,要调用C++的库了  //会进入本地方法栈,然后调用本地方法接口JNI(Java Native Interface)
进入本地方法栈,然后调用本地方法接口JNI(Java Native Interface)
JNI的作用:扩展Java的使用,一般用于驱动硬件

 

三种JVM

Sun公司:HotSpot、OpenJDK

其他:JRockit、J9VM

 

程序计数器PC

每一个线程私有的,指向下一条指令的地址

PC寄存器:仅存一个指针,占用空间非常小

 

JVM常用命令

    |_  jstat查看虚拟机统计信息
    |_  jmap + MAT实战内存溢出
    |_  jstack 实战死锁循环与死锁

 

方法区(内含常量池)

方法区被所有线程共享,

方法区含有:静态变量static、常量final、类信息(构造方法、接口定义)、运行时的常量池

=>  static + final + Class + 常量池

(堆:实例变量)

 

  每一个方法内部结构 =》栈帧

栈内存的生命周期 和线程同步

线程结束,栈内存就释放了;所以对于栈来说,不存在GC垃圾回收

栈的内容:8大基本类型 + 对象引用 + 实例的方法

 

栈溢出:StackOverflowError 是比较严重的错误

 

 

 


Heap,一个JVM只有一个堆内存,

堆内存的大小是可以调节的

  =》 这两个用于调参

 

堆中的内容:

  实例对象中的 方法、常量、变量

 

堆内存细分为3个区域:

  新生区 young 

  老年区 old

  永久区 perm

新生区-伊甸园 ,经过轻GC,到幸存区,再经过重GC,到老年区;

层层筛选后,等老年区也满了,就会OOM内存溢出

经验:99%的对象都是临时对象。

新生区

 

老年区

 

永久区(元空间)

==》元空间在逻辑上存在,但是在物理上是不存在的

元空间,常驻内存。关闭JVM会释放这个区域的内存。

用来存放JDK自身携带的Class对象,被所有的线程、对象共享;

Interface元数据,存储的是Java运行时的一些环境or类信息,这个区域不存在垃圾回收

 

永久区OOM:

  当一个启动类,加载了大量的第三方jar包;

  Tomcat部署了太多的应用;

  大量动态生成的反射类

 

JVM调优

常用JVM调优参数

堆内存调优

public class Test {
    public static void main(String[] args) {
        long max = Runtime.getRuntime().maxMemory();
        System.out.println("max = " + max/1024/1024 + "MB");
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("total = " + total/1024/1024 + "MB");
        //默认max=1/4的总内存; total=1/64的总内存
    }
}

这台电脑的内存为32GB

 

 Configuration里面的VM Options设置为:

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

  jvm参数非常非常多,可以到网上去查到

  这里的总空间=新生区+老年区,不包括逻辑上的元空间

 

制造OOM:

-Xms2m -Xmx2m -XX:+PrintGCDetails  ##设置堆的大小为2m
public class Test {
    public static void main(String[] args) {
        long max = Runtime.getRuntime().maxMemory();
        System.out.println("max = " + max/(double)1024/1024 + "MB");
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("total = " + total/(double)1024/1024 + "MB");
        //默认max=1/4的总内存; total=1/64的总内存

        String str = "123";
        while(true){
            str+="111";
        }
    }
}

 

解决OOM:

1)扩大堆内存,试下能不能解决

2)内存快照分析工具

 Jprofiler 作用:

  • 分析Dump内存文件,快速定位内存泄漏
  • 获得堆中的数据
  • 获得大的对象

 插件

 设置dump导出错误信息:

-Xms1m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
public class Test {
    public static void main(String[] args) {
        long max = Runtime.getRuntime().maxMemory();
        System.out.println("max = " + max/(double)1024/1024 + "MB");
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("total = " + total/(double)1024/1024 + "MB");
        //默认max=1/4的总内存; total=1/64的总内存

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

        ArrayList<Test>list = new ArrayList<Test>();
        int count = 0;

        try {
            while(true){
                list.add(new Test());   //问题所在
                count++;
            }
        } catch (Error e) {
            System.out.println("count = " + count);
            e.printStackTrace();
        }

    }
}

执行上述程序,生成:Dumping heap to java_pid3044.hprof

1)双击打开,查看

 就知道哪里导致占用大量内存的问题了

 

2)点击 

 

 

 

GC:

垃圾回收主要针对堆,  (少量方法区)

堆空间

主要划分为:新生区、老年区、永久区

新生区分为:伊甸园区、幸存区(两半,0区+1区,轮流倒)8:1:1         (8:1:1比例可以调,留存率高就降低伊甸园区大小)

需要回收:死对象

判断对象生死?两个方法:

1)引用计数器法:

给对象添加一个引用计数器,每当被引用时加 1,失效时减 1,任何时刻计数器为 0 的对象是不可能再被引用的。

问题:如果两个对象循环引用,则计数器永远不为0,无法回收。

2)可达性分析算法:

每个对象,抽象为一个Node节点,

从一系列GC Roots Set 中的根出发,不断遍历,如果没有任何引用相连,则不可达。

(这样可以解决循环引用问题)

 

 

Java中4种引用类型: 

JDK1.2 之后,将引用分为:强引用、软引用、弱引用、虚引用,强度依次减弱。

总结:强引用只要还在就不会回收,软引用会在溢出前回收,弱引用在下次回收时回收,虚引用仅会在回收时发送通知。

 

 

强引用(最常用):

  普遍存在,类似 Object obj = new Object(),只要强引用还存在,GC 就无法回收它。

软引用:

  描述一些还有用但非必须的对象。系统快发生溢出前,会把这些对象列进回收范围之中进行第二次回收。SoftReference 类来实现。

弱引用:

  描述非必须对象,此类对象只能生存到下一次垃圾回收发生之前,无论内存是否够,都会回收。WeakReference 类实现。

虚引用:

  唯一目的是能在这个对象被GC时收到一个通知,不会影响对象生存时间也不会获得对象实例。PhantomReference 类实现。

 

 

 

GC  4个常用算法

1)引用计数法:

缺点:计数器本身损耗很多性能,效率不高

2)复制算法:

  ==》适用于复制率较低的 新生区(含:伊甸园区+2个幸存区)

两个幸存区轮流倒,from=>to,谁空谁是to

每次GC后,Eden区、from-幸存区 都是被倒空的

当1个对象经历15次GC,都还没有死,就会荣升 老年区

优点:没有内存的碎片,速度快

缺点: to区永远是空的,浪费了不少内存空间

3)标记-清除法(两轮)

flag标记是否存活,一次扫描是否存活、一次扫描清除死的对象

缺点:两次扫描浪费时间;内存碎片

优点:不需要额外的空间(相对于复制算法)

4)标记-整理法(标记+清除+压缩)(三轮)

防止内存碎片:增加一轮扫描,将对象移到一端;

 

 

5)分代收集算法

新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。

老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。

大部分情况对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 Survivor 区,对象年龄加 1,当年龄增加到一定默认 15 岁时,就会被晋升到老年代中。同时,大对象将直接进入老年代。

 

3维度比较——时间、空间、碎片

(下面均是越左越好)

时间消耗:复制算法 < 标记清除 < 标记压缩

内存碎片:复制算法 = 标记压缩 < 标记清除

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

 

GC:分代收集算法

新生代GC(轻GC):(Minor GC)(伊甸园=》幸存区)

  存活率低、复制率低;轻GC非常频繁

  => 复制算法(15次没死  进入老年区)

老年代GC(重GC):(Major GC/Full GC)(幸存区=》老年区)

  区域大,存活率高;

  => 标记清除+标记压缩,混合实现(几次清除,一次压缩)

 

(老年区满了OOM)解决:

1)扩大堆内存,试下能不能解决

2)内存快照分析工具Jprofiler

 

 

垃圾收集器

主要5种: Serial、ParNew、Parallel Scavenge、CMS、G1

Serial:

单线程;简单而高效(与其他收集器的单线程相比);

新生代采用复制算法,老年代采用标记-整理算法。

ParNew:

ParNew 收集器,其实就是 Serial 的多线程版本

 

Parallel Scavenge:

几乎和 ParNew 一样,区别是:优化了CPU利用率,吞吐量提高了。

可设置 Parallel 收集器+ 老年代串行/老年代并行。

CMS:

是一个很好的收集器。GC的时候,用户线程几乎不停顿。

它是基于“标记-清除”算法。

优点:并发收集、低停顿

缺点:它用了“标记-清除”算法,会导致空间碎片

4 个步骤

1.初始标记暂停其他线程,并记录下直接与 root 相连的对象,速度很快。

2.并发标记:  GC 和用户线程,同时开启,记录可达对象(标记法、可达法)

3.重新标记暂停其他线程,更新上一阶段变动的。

4.并发清除开启用户线程,同时 GC清理

 

G1:(Garbage-First)

基于“标记-整理”算法

优先回收  价值最大 Region

它针对:多处理器、大内存

G1 主要有并行与并发、分代收集、空间整合、可预测的停顿的特点。运作大致分为初始标记、并发标记、最终标记、筛选回收这四个过程。

 

posted @ 2021-05-07 16:28  青杨风2199  阅读(67)  评论(0编辑  收藏  举报