zoukankan      html  css  js  c++  java
  • 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寄存器:仅存一个指针,占用空间非常小

    方法区(内含常量池)

    方法区被所有线程共享,

    方法区含有:静态变量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 主要有并行与并发、分代收集、空间整合、可预测的停顿的特点。运作大致分为初始标记、并发标记、最终标记、筛选回收这四个过程。

  • 相关阅读:
    PHP数组(数组正则表达式、数组、预定义数组)
    面向对象。OOP三大特征:封装,继承,多态。 这个讲的是【封存】
    uvalive 3938 "Ray, Pass me the dishes!" 线段树 区间合并
    LA4329 Ping pong 树状数组
    HDU 1257 最少拦截系统
    HDU 1260 Tickets
    codeforce 621D
    codeforce 621C Wet Shark and Flowers
    codeforce 621B Wet Shark and Bishops
    codeforce 621A Wet Shark and Odd and Even
  • 原文地址:https://www.cnblogs.com/qyf2199/p/14732006.html
Copyright © 2011-2022 走看看