JVM入门

jvm入门

jvm的位置

jre包含jvm

jvm在操作系统上(Windos/Linux/Mac)

操作系统在硬件上(Intel/Spac...)

c5okUf.png

jvm的体系架构

c5oRsA.png

类加载器和双亲委派

类加载器有哪些?

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器:加载jre\lib下的rt包
  3. 扩展类加载器:加载jre\lib\ext下的包
  4. 应用程序加载器:加载程序员写的java文件

双亲委派机制:安全

  1. 类加载器收到类加载的请求
  2. 将这个请求委托给父类加载器去完成,一直向上委托,直到启动类(根)加载器
  3. 类加载器检查能否加载这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子类加载器进行加载
  4. 重复步骤3

c5TpJU.png

Native和方法区(Method Area)和程序计数器(Program Counter Register)

Native

package com.edgar;

public class Demo {

    public static void main(String[] args) {
        new Thread(()->{

        },"my thread name").start();
    }

    //native:凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层C语言的库!
    //会进入本地方法栈
    //调用本地方法本地接口 JNI--java native interface
    //JNI的作用:扩展java的使用,融合不同的编程语言为Java所用! 最初:C、C++
    //Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序
    //它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法
    //在最终执行的时候,加载本地方法库中本地方法,通过JNI
    public native void start0();
}

程序计数器(PC寄存器)

程序计数器(Program Counter Register)是一块较小的内存空间,标记当前线程所在的行号。

特点:
  • 线程私有
  • JVM规范中唯一没有规定OutOfMemoryError情况的区域
  • 如果正在执行的是Native 方法,则这个计数器值为空

方法区

线程共享

静态变量(static)、常量(final)、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关

栈(Stack)

1.栈:数据结构

程序:数据结构+算法:持续学习~

程序:框架+业务逻辑:吃饭~

栈:先进后出、后进先出:水桶效应

水桶有底部,A要出来,B必须先弹出

cIhtZ6.png

队列:先进先出(FIFO:First Input First Output)

队列类似管道,底部是空的,A要出来,直接就出来了

cIhEMn.png

栈:栈内存,主管程序的运行,生命周期和线程同步;

线程结束,栈内存就释放了,对于栈来说,不存在垃圾回收问题

一旦线程结束,栈就Over!

栈:8大基本类型(局部变量)+对象引用+实例的方法

栈溢出:StackOverFlowError

java中的基本数据类型一定存储在栈中吗?

一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因

在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。

​ (1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中

​ (2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。

二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。

​ 同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量

​ (1)当声明的是基本类型的变量其变量名及其值放在堆内存中的

​ (2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

三种jvm

  • Sun公司HotSpot Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

  • BEA JRockit

  • IBM J9 VM

堆(Heap)

Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的

类加载器读取了类文件后,一般会把什么文件放在堆中?类,方法,常量,变量~,保存我们所有引用类型的真

实对象;

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区,两个幸存区)yooung/new
  • 养老区 old
  • 永久区 perm

cI7Uot.md.png

GC垃圾回收,主要在伊甸园区快满了和养老区快满了的情况下触发

假设内存满了,报OOM(OutOfMemoryError),堆内存不够!

在JDK8以后,永久存储区改了个名字(元空间);

新生区

  • 类:诞生和成长的地方,甚至死亡
  • 伊甸园区:所有对象都是在伊甸园区new出来的
  • 幸存区(0,1)

养老区

幸存区的对象经过多次gc之后始终存活,进入养老区

永久区

这个区域常驻内存的。用来存放jdk自身携带的Class对象。interface元数据,存储的是java运行时的一些环境或

类信息,这个区域不存在垃圾回收!关闭VM虚拟机就是释放这个区域的内存

一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用。大量动态生成的反射类不断被加载。直到

内存满,就会出现OOM;

  • jdk1.6之前:永久代,常量池在方法区
  • jdk1.7:永久代,但是慢慢的退化了,去永久代,常量池在堆中
  • jdk1.8之后:无永久代,常量池在元空间。

cIxXtJ.md.png

堆内存调优

package com.edgar;

public class Demo02 {

    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        //返回jvm的初始化总内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max="+max+"字节"+(max/(double)1024/1024)+"MB");
        System.out.println("total="+total+"字节"+(total/(double)1024/1024)+"MB");

        //默认情况下:分配的总内存 是电脑内存的1/4,而初始内存为1/64

        //OOM如何处理
        //1.尝试扩大堆内存看结果 -Xms10000m -Xmx10000m -XX:+PrintGCDetails
        //2.分析内存,看一下哪个地方出现了问题(专业工具)
        
       
    }
}

在一个项目中,突然出现了OOM故障,那么该如何排除~研究为什么出错

  • 能够看到代码第几行报错:内存快照分析工具,MAT,Jprofile
  • Debug,一行行分析代码(不推荐)

MAT,Jprofile的作用:

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

注意:控制台其实有OOM报错信息,线上项目可以看日志

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.edgar.Demo03.<init>(Demo03.java:7)
	at com.edgar.Demo03.main(Demo03.java:14)

GC

jvm在进行GC时,并不是对这三个区域统一回收。大部分时候,回收的是新生代~

  • 新生代
  • 幸存区(from,to)
  • 老年区

GC两种类:轻GC(普通的GC)、重GC(全局的GC)

轻GC:主要清理新生代,偶而当幸存区满了,会去清理幸存区

重GC:全部清理(新生代、幸存区、老年区)

GC题目:

  • JVM的内存模型和分区~详细到每个区放什么?
  • 堆里面的分区有哪些?Eden,from,to,老年区,说说他们的特点!
  • GC的算法有哪些?标记清楚法,标记压缩,复制算法,引用计数器,怎么用的
  • 轻GC和重GC分别在什么时候发生?

GC常用算法:

引用计数法

C对象被用到的次数为0,被清除

coF5LR.md.png

复制算法

  • 好处:没有内存碎片
  • 坏处:浪费了空间~:多了一半空间永远是空to。假设对象100% 存活(极端情况),复制消耗资源大

复制算法最佳使用场景:对象存活度较低的时候;新生区~

cTDCod.png

标记清除压缩算法

cTrqv6.png

总结

内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:复制算法=标记压缩算法>标记清除算法

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

思考一个问题:难道没有最优算法嘛?

答案:没有,没有最好的算法----->GC:分代收集算法

年轻代:

  • 存活率低
  • 复制算法!

老年代:

  • 区域大,存活率高
  • 标记清除+标记压缩算法混合实现 (内存碎片不多,可多次清除,再压缩)

深入理解JVM可去看书<<深入理解java虚拟机>>--周志明

posted @ 2021-04-19 22:46  EdgarStudy  阅读(66)  评论(0编辑  收藏  举报