JVM作用、结构、调优、内存管理

JVM的作用
JVM的结构
JVM调优: 逃逸分析、栈上分配、 标量替换
JVM的内存管理、垃圾回收: which、when、 how:


jvm的作用

1.将编译生成的class文件解释成机器识别的机器指令,就可以实现java程序的跨平台特性
2.进行内存管理

JVM的内存结构

image.png
程序计数器:(线程私有)

它的作用是保存线程当前所执行字节码的行号。

为什么要有程序计数器呢?
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

java栈(线程私有)

作用:保存基本数据类型和对象的引用。
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈

本地方法栈保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不再为其在虚拟机栈中创建栈帧,jvm只是简单的动态链接并直接调用native方法。

java堆(线程共享)

作用:就是存放对象实例,几乎所有的对象实例都在这里分配内存。
是垃圾收集器管理的主要区域,Java堆中还可以细分为:新生代和老年代;新生代还可以分为有Eden区、From Survivor区、To Survivor区等,默认比例8:1:1

方法区(线程共享)

它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

jdk1.8以前,永久代是方法区的一种实现,但是因为主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出,所以jdk1.8元空间取代了永久代,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制

运行时常量池:

用于存放编译器生成的各种字面量("zdy","123"等)和符号引用,jdk1.6运行时常量池在方法区中,jdk1.7以后都在堆中。

JVM参数调优

  1. Java对象分配流程


    image.png
  2. 栈上分配
    本质:Java虚拟机提供的一项优化技术
    基本思想: 将线程私有的对象打散分配在栈上
    优点:
    (1)可以在函数调用结束后自行销毁对象,不需要垃圾回收器的介入,有效避免垃圾回收带来的负面影响
    (2) 栈上分配速度快,提高系统性能
    局限性: 栈空间小,对于大对象无法实现栈上分配
    技术基础: 逃逸分析、标量替换

3.逃逸分析:
逃逸分析的目的: 判断对象的作用域是否超出函数体[即:判断是否逃逸出函数体]
开启逃逸分析:-XX:+DoEscapeAnalysis
逃逸分析技术在 Java SE 6u23+ 开始支持,并默认设置为启用状态,可以不用额外加这个参数。
4.标量替换:
启用标量替换,允许对象打散分配到栈上
开启标量替换:-XX:+EliminateAllocations
标量替换同样在 JDK8 中都是默认开启的,并且都要建立在逃逸分析的基础上。

public class StackAlloc {

    public static class User{
        public int id = 0;
        public String name = "";
    }

    public static void alloc(){
        User u= new User();
        u.id = 5;
        u.name = "cyy";
    }

    public static void main(String[] args) {
        long b = System.currentTimeMillis();
        for (int i=0;i<100000000;i++){
            alloc();
        }
        long e  = System.currentTimeMillis();
        System.out.println((e-b)+"ms");
    }
}

jvm参数设置:

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations -XX:-UseTLAB

若没有开启标量替换:

image.png

若开启标量替换:


image.png

JVM的内存管理、垃圾回收: which、when、 how:

which:

对象存活判断

判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

在java,可作为GC Root的对象包括:
1.方法区:类静态属性引用的对象;
2.方法区:常量引用的对象;
3.虚拟机栈(本地变量表)中引用的对象;
4.本地方法栈JNDI(Native方法)中引用的对象;

when:

minor gc:(发生在新生代的垃圾收集动作)
触发条件主要是Eden区域满了

full gc:(收集整个堆)
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间,如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
(5)虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间,如果小于,如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升(晋级老年代对象的平均大小)平均值的大小,如果小于直接执行FullGC

how:

采用分而治之的思想,将对内存划分为老年代和新生代,在HotSpot中,新生代:老年代=1:2,新生代采取复制算法,而老年代采取标记清理或者标记压缩的算法
1、新生代=(Eden:Survivor from:Survivor to = 8:1:1)回收策略:
(1)首先创建对象,存储Eden+from区域,一旦没有足够的内存进行分配,虚拟机发起一次Minor GC,之后如果存活对象内存小于to区域内村,将存活的对象放置到to区域,并且将对象年龄+1,如果有大对象直接进入老年代,如果年龄大于阈值(默认是15),则直接进入老年代。
(2)之后继续创建对象,存储到Eden+to区域,一直会保持一个Survivor空白
(3)当连续分配对象的时候,对象会逐渐从eden到 survivor到老年代,最终OOM

新生代的空间分配担保
在发生minor gc之前,虚拟机会检测 : 老年代最大可用的连续空间>新生代all对象总空间
1、满足,minor gc是安全的,可以进行minor gc。
2、不满足,虚拟机查看HandlePromotionFailure参数:
(1)为true,允许担保失败,会继续检测老年代最大可用的连续空间>历次晋升到老年代对象的平均大小。若大 于,将尝试进行一次minor gc,若失败,则重新进行一次full gc。
(2)为false,则不允许冒险,要进行full gc(对老年代进行gc)。

2、老年代回收策略:老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收

你可能感兴趣的:(JVM作用、结构、调优、内存管理)