jvm

Posted by 小兵兵 on Sunday, December 8, 2019

目录

jvm体系结构

黄色:所有线程共享、占用空间较大,存在垃圾回收

灰色:各个线程独享数据区域、占用空间较小,不存在垃圾回收

1575783857812

类装载器ClassLoader

是什么

负责加载class文件,class文件在文件开头有特定的文件标示,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则有Execution Engine决定。

1575784373433

类装载器类型

虚拟机自带的加载器

  • 启动类加载器(Bootstrap)C++
  • 扩展类加载器(Extension)Java
  • 应用程序类加载器(AppClassLoader)Java也叫系统类加载器,加载当前应用的classpath的所有类

用户自定义加载器

  • Java.lang.ClassLoader的子类,用户可以定制类的加载方式

1575784997347

演示一

1575790437359

双亲委派

双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。

沙箱安全

沙箱机制是由基于双亲委派机制上 采取的一种JVM的自我保护机制,假设你要写一个java.lang.String 的类,由于双亲委派机制的原理,此请求会先交给Bootstrap试图进行加载,但是Bootstrap在加载类时首先通过包和类名查找rt.jar中有没有该类,有则优先加载rt.jar包中的类,因此就保证了java的运行机制不会被破坏

演示二

1575790989071

本地接口

 本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraties

  目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或着Java系统管理生产设备,在企业级应用中已经比较少见。应为现在的异构领域间的哦通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍。

本地方法栈

 它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库。

演示三

线程的start方法调用了start0方法就是一个本地方法,只有声明没有实现

1575791717214

程序计数器

类似排班值日表

1575791524021

方法区

  • 所有线程共享,存在垃圾回收
  • 存类模板信息(Car Class)
  • 方法区是一种规范
    • Java7是永久代
    • Java8是元空间
  • 实例变量存在堆内存中,和方法区无关

Java栈

栈管运行,堆管存储

队列(FIFO)先进先出

(FILO)先进后出

Java栈保存:8种基本数据类型、对象的引用变量、实例方法

Java中层面main方法是程序的入口,main方法会被保存到Java栈中,Java栈中层面叫做栈帧

演示四

方法递归调用,栈溢出

1575794149620

栈、堆、方法区的交互关系

创建对象的过程Object obj = new Object()obj存储在Java中,实例对象数据存储在中,实例对象数据是由类模板创建出来的,类模板存储在方法区

1575794350515

堆结构

逻辑上分

  • 新生代
    • 伊甸区
    • 幸存0区
    • 幸存1区
  • 老年代
  • 永久代(Java7)、元空间(Java8),方法区是一种规范,永久代或者是元空间是方法区的不同落地实现

对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。

1576051892566

物理上则排除了永久代或者是元空间

1576051849603

证明堆结构

package com.zbiti.jvm;

public class HeapStructureDemo {
    public static void main(String[] args) {
        //返回 Java 虚拟机试图使用的最大内存量。
        long maxMemory = Runtime.getRuntime().maxMemory();
        //返回 Java 虚拟机中的内存总量。
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double) 1024 / 1024) + "MB");
        System.out.println("TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double) 1024 / 1024) + "MB");
    }
}

添加VM options :-XX:+PrintGCDetails

1576052345574

控制台输出

可以看到输出了堆结构:新生代PSYoungGen、老年代ParOldGen、元空间Metaspace,那如何知道物理上堆结构只包含新生代和老年代呢

新生代加上老年代的和38400K+87552K=125952K

堆内存的默认初始化大小128974848字节128974848/1024=125952K,因此可知物理上堆结构只包含新生代和老年代

1576053646764

堆内存调优

Java7

1576054181737

Java8

1576054230377

参数 说明
-Xms 设置初始分配大小,默认为物理内存的1/64
-Xmx 最大分配内存,默认为物理内存的1/4
-XX:+PrintGCDetails 输出详细的GC处理日志

修改堆参数,模拟堆溢出

-Xms1024m -Xmx1024m -XX:+PrintGCDetails,设置堆初始值和最大值一样,避免峰值的低、高抖动

package com.zbiti.jvm;

import java.util.Random;

public class HeapSpaceDemo {
    public static void main(String[] args) {
        String str = "com.zbiti.com";
        while(true){
            str+=str+new Random().nextInt(88888888)+new Random().nextInt(999999999);
        }
    }
}

1576200018621

控制台

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

1576199860861

4大垃圾回收算法

1576203461842

JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。因此GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC)

  • 普通GC(minor GC):只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
  • 全局GC(major GC or Full GC):指发生在老年代的垃圾收集动作,出现了Major GC,经常会伴随至少一次的Minor GC(但并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上

引用计数法

1576293394802

复制算法(Copying)

使用在新生代

1576293534032

绿色:空闲的堆内存空间(新生代)

红色:可回收的垃圾对象

黄色:对象已经占用的堆内存空间

蓝色:堆内存空间(老年代)

1576293674987

标记清除(Mark-Sweep)

使用在老年代

1576294271165

1576294283136

标记压缩(Mark-Compact)

使用在老年代,比标记清除多一步压缩

1576295775283