黑马程序员技术交流社区

标题: 【西安校区】JVM笔记(一) [打印本页]

作者: 逆风TO    时间: 2020-1-2 13:36
标题: 【西安校区】JVM笔记(一)
本帖最后由 逆风TO 于 2020-1-2 13:45 编辑



Class Loader类加载器
负责加载class文件,class文件在文件开头有特定的文件标识,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

Method Area方法区(永久区)
方法区是被所用的线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也是再此定义。简单说,所有定义的方法的信息都报错在该区域,此区间属于共享区间。
静态变量+常量+类信息+运行时常量池存在方法区中,它存储了每一个类的结构信息

注意:Java8去永久代,用元空间 替代。永久代使用的是JVM的堆内存,但是元空间并不在虚拟机中华而是使用本机的物理内存。

Heap堆
栈管运行,堆管存储

堆是线程共享区域,是java虚拟机管理最大的一块区域。此区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

PC Register 程序计数器
每一个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。线程私有。此区域是唯一一个没有OutOfMemoryError(OOM)的区域。

VM Stacks 虚拟机栈
栈管运行,堆管存储

线程私有。

虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

Native Method Stack 本地方法栈
    它的具体做法是  Natice Method  Stack 中登记native 方法,在Execution Engine 执行时加载native libraies。和虚拟机栈一样,都是线程私有的。

Native Interface
  本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,想要立足,必须有调用C/C++程序。目前,该方法使用的越来越少了,因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以用WebService等等。
栈:一般指虚拟机栈
1)对于栈来说不存在垃圾回收问题。

2)基本类型的变量和对象的引用变量都在函数的栈内存中分配。

栈帧
栈帧(Stack Frame)是用于支持虚拟机运行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

堆内存示意图
Eden:S0:S1=8:1:1

新生代:老年代=1:2 (新生代占堆的1/3,老年代占堆的2/3)

在新生代中存活15(默认)次以上才被放入老年代中


堆内存调优
参数        含义        demo
-Xms        设置初始分配大小,默认为物理内存的“1/64”        -Xms5m
-Xmx         最大分配内存,默认为物理内存的“1/4”        -Xms20m
-XX:+PrintGCDetails         输出详细的GC处理日志        -XX:+PrintGCDetails
-XX:+PrintCommandLineFlags         可以将隐式或者显式传给虚拟机的参数输出        XX:+PrintCommandLineFlags
-Xmn        设置新生代的绝对大小,新生代一般会设置整个堆空间的1/3到1/4左右        -Xmn20m
-XX:SurvivorRatio        用来设置新生代中eden空间和form/to空间的比例                                                                -XX:SurvivorRatio=eden/from=eden/to        -XX:SurvivorRatio=2
-XX:NewRatio        设置新生代和老年代的比例 -XX:NewRatio=老年代/新生代        -XX:NewRatio=2
-Xss        制定线程的最大栈空间,整个参数也直接决定了函数课调用的最大深度        -Xss5m
-XX:MaxTenuringThreshold        新生代经过多少次GC后进入老年代,默认为15        -XX:MaxTenuringThreshold=15
-XX:PretenureSizeThreshold        设置对象的大小超过在制定的大小之后,直接晋升老年代 ,后面的1000为100k        -XX:PretenureSizeThreshold=1000

在实际工作中,我们可以直接将初始的堆大小与最大堆大小设置相等,这样的好吃是可以减少程序运行时的垃圾回收次数,从而提高性能。

收集算法
口诀
频繁收集Yong区
较少收集Old区
基本不动Perm区
标记清除算法
老年代一般是由标记清除或者是标记清除与标记整理的混合实现
优点:不需要额外空间(和复试算法的缺点2比)
缺点:  1)有碎片,空闲空间不连续
            2)两次扫描,效率低
             3)需要一个内存空间记录哪是空,哪是非空

复制算法
年轻代中使用的Minor GC  ,这种GC算法采用的是复制算法(Copying)
优点:没有碎片
缺点:1)如果对象存活率很高(极端一点,100%),将全部数据复制费时费力
          2)浪费一了   survice 1 的空间
标记整理算法
老年代一般是由标记清除或者是标记清除与标记整理的混合实现
优点:无碎片,不需要额外空间
缺点:效率低,低于复制算法

垃圾收集器

Serial收集器
serial收集器依然是虚拟机运行在Client模式下的默认新生代收集器。
复制算法收集器。
ParNew收集器
ParNew收集器是Serial收集器的多线程版本。
复制算法收集器。
Parallel Scavenage收集器
关注点为吞吐量
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
复制算法收集器
Serial Old收集器
标记-整理算法
Serial收集器的老年版
Parallel Old收集器
标记-整理算法
在注重吞吐量和CPU资源敏感的场合,都可以优先考虑Parallel Scavenge 加 Parallel Od
Parallel Old是Parallel Scavenge收集器的老年带版本
CMS收集器
标记-清除算法
CMS收集器数一种以获取最短回收停顿时间为目标的收集器。
收集器步骤:
1)初始标记
2)并发标记
3)重新标记
4)并发清除
优点:并发收集、低停顿
缺点:CMS收集器对CPU资源非常敏感
          无法处理浮动垃圾
          标记-清除法的缺点,有碎片
G1收集器
特点:并发与并行、分代收集、空间整理、可预测停顿
类加载机制




注意:
加载会调用静态代码块
初始化会调非静态代码块和构造方法
先加载类在初始化类,加载只加载一次
加载和初始化不一样
加载的是类的模板

初试化的是类的实例对象
1)静态的大于代码块,代码块大于构造方法
静态块先执行,并且只执行一次
代码块比构造方法先执行,并行new几次运行几次(运行结果)
public class Person {

    public Person() {
        System.out.println("Person 构造方法");
    }

    static {
        System.out.println("Person 静态代码块");
    }

    {
        System.out.println("Person 普通代码块");
    }

}

public class Start {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println("-------");
        Person person2 = new Person();
    }
}


2)通过数组定义引用类,不会触发此类的加载
通过数组定义引用类,不会加载此类,因为加载此类必定会调用static静态代码快
public class Start {
    public static void main(String[] args) {
        Person[] persons = new Person[10];
    }
}


3)加载子类会同时加载和初始化父类
先执行父类的静态块(看下图运行结果)
在执行子类的静态块(看下图运行结果)
在执行父类的普通块和构造方法(看下图运行结果)
最后执行子类的普通块和构造方法(看下图运行结果)
public class Man extends Person {
    public Man() {
        System.out.println("Man 构造方法");
    }

    static {
        System.out.println("Man 静态代码块");
    }

    {
        System.out.println("Man 普通代码块");
    }
}
public class Start {
    public static void main(String[] args) {

        Man man = new Man();
    }
}


4)通过子类引用父类的静态变量,不会导致子类加载和初始化,会导致父类加载,不会导致父类初始化
通过子类使用父类的静态变量,不会加载子类,会加载父类
会调用父类的静态代码块,不会调用父类的普通代码块
不会加载子类,自然不会调用子类的静态代码块
public class Person {

    public Person() {
        System.out.println("Person 构造方法");
    }

    static {
        System.out.println("Person 静态代码块");
    }

    {
        System.out.println("Person 普通代码块");
    }


    //静态成员变量
    public static int value = 1024;

}

class Man extends Person {
    static {
        System.out.println("Man 静态代码块");
    }
}


public class Start {
    public static void main(String[] args) {
        System.out.println(Man.value);
    }
}


5)常量(final)在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的加载和初始化。
public class Person {

    static {
        System.out.println("Person 静态代码块");
    }

    {
        System.out.println("Person 普通代码块");
    }

    //静态final成员变量
    public static final int value = 1024;

}


public class Start {
    public static void main(String[] args) {
        System.out.println(Person.value);
    }
}










欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2