JVM详解

一、JVM的内存布局

目录

一、JVM的内存布局

1.内存的划分

2.和内存相关的异常

二、java的类加载

1.类加载的基本流程

2.类加载中的双亲委派模型

3.双亲委派模型的优点

四、垃圾回收机制(GC)

1.什么是垃圾回收

2.Java的垃圾回收要回收的内存是哪些?

3.回收堆上的内存具体回收的是什么?

4.如何找到垃圾 

5.找到垃圾后如何回收

5.1 标记回收

5.2 复制算法

5.3 标记-整理

5.4 分代算法


1.内存的划分

JVM划分的区域:
1.堆
2.栈
3.方法区
4.程序计数器

JVM实际上是一个java进程,进程是用来 管理硬件资源的,比如内存.JVM启动之后就会从操作系统这里申请到一大块内存

对于堆区和方法区,在整个JVM中只存在一份,而程序计数器和栈区是跟进程绑定在一起的,每个不同的线程都有独立的一份程序计数器和栈区

不同的区域放不同的东西
1.堆中 放入的是new的对象(JDK1.8中,字符串常量池在堆中)
2.方法区放入的是类对象.
.java->.class->JVM就 会把.class文件进行加载,加载到内存中,最后变为类对象.类的static成员作为类属性.同样也是在类对象当中,就放到方法区.

类对象里有什么?
1.包含这个类的各种属性的名字,类型,访问权限.
2.包含这个类的各种方法的名字的名字,返回值,访问 权限,参数类型,以及方法的实现的二进制代码
3.包含了这个类的static成员.

方法区内部有个运行时常量池,存放字面量和符合引用:
字面量:final常量、基本数据类型的值.

程序计数器放入的是内存地址.
这个内存地址的含义是接下来要去执行的指令地址.
我们写的.java文件->.class文件->读到内存中->每个指令都有自己的地址->CPU要执行指令就需要从内存中取地址,然后再在CPU上执行

2.和内存相关的异常

1.堆溢出,代码中出现堆溢出的话就会抛出java.lang.OutOfMemoryError,典型的情况就是不断的去new对象而不去释放内存.

2.栈溢出:代码中出现栈溢出的话就会抛出"java.lang.StackOverflowError",典型的场景就是不断去递归不带有终止的i条件.栈里面除了放局部变量外 ,还要放方法调用关系.

二、java的类加载

1.类加载的基本流程

类的生命周期:都离不开.class文件的格式

加载:目的是把.class文件给找到.如果代码中需要加载某个类,就需要去特定的目录下去查找该.class文件,找到之后,就需要打开这个文件,并且读取到这个文件.此时这些打开数据就已经读到内存里了.

验证:目的是验证后缀为.class的文件是否是编译器生成的,如果是人为地去改后缀变为.class的文件,那么就是不合法的.class文件.除了验证.class文件的格式外,还需要验证文件里面的字节码指令是否正确.(方法里具体要执行的指令)

准备:目的是为类对象中的一些成员变量分配内存空间(静态变量....),并且进行一个初步的初始化(初始空间大小为0).

解析:主要是针对字符串常量进行的处理..class文件涉及到一些字符串常量,在 解析的过程中,就把这些字符串常量替换成当前JVM中的字符串常量.
注 :不是程序一启动,就把所有的类都加载完毕的,而是用到哪个类就加载哪个类,而字符串常量是最初启动JVM的时候就有的.

初始化:主要针对在"准备"环节中,对初步初始化的静态变量进行真正地初始化.同时也会执行static的代码块.

2.类加载中的双亲委派模型

双亲委派模型是类加载中的加载环节里面的很小的一部分细节,更准确地说,应该叫"父亲委派模型".

在进行类加载的过程中,其中一个非常重要的环节就是根据这个类的名字(如:java.lang.String)找到对应的.class文件.在jvm中,有三个类加载器(三个特殊的对象)来负责找文件的操作.这三个类加载器对象都有各自找的区域.

 这三个类加载器之间存在父子类关系(但并不是继承中的父子关系,而且类似于链表一样,每个类里面都有parent字段,指向了父类加载器).

这里其实就是在约定上述被扫描目录的优先级.这个优先级在正常情况下没有什么作用,假设如果是我们自己创建了一个java.lang.String的类(只有一个类).同时有标准库中的String类,那么有优先级,就会先加载标准库的String类,因为我们创建的类是一个复合类,因为就没有其他加载器的事情了.

3.双亲委派模型的优点

1.避免重复加载类:比如A类和B类都有一个父类C类,那么当A启动时就会将C类加载起来,那么B类进行加载时不需要在重复加载C类了.
2.安全性:使用双亲委派模型也可以保证java的核心API不被 篡改,如果没有使用双亲委派模型,而是每个类加载器自己的话就会出现一些问题,比如我们编写一个称为java.lang.Object类的话,那么程序运行的时候,系统就会出现不同的Object类,而有些Object类又是用户自己提供的,因此安全性就不能保证了.

四、垃圾回收机制(GC)

1.什么是垃圾回收

垃圾回收,回收的是内存.JVM其实是一个进程,一个进程会持有很多硬件资源,如(CPU,内存,硬盘,带宽),而且系统的内存总量是一定的.因此对内存的合理使用是非常重要的
内存的要经过:申请->使用->释放过程.内存是有限的,并且要给很多的进程去使用.从代码编写的角度来看,内存申请的时机是明确的,但内存的释放时机是模糊的.

java采用垃圾回收的方式,对于该机制来说,哪一个代码申请都可以,哪里申请都可以,都是统一由JVM统一进行垃圾回收(内存释放),具体来说,就是由JVM内部的一组专门负责垃圾回收的线程来进行这样的工作.

优点:能够非常好地保证不出现内存泄漏的情况(不是100%保证),并且是自动去进行内存释放.

缺点:
1.需要消耗额外的系统资源.
2.内存释放可能存在延时(不是内存不用了就马上回收,可能过段时间回收).
3.可能会出现STW问题,比如有一大段内存需要释放,那么系统的资源都用来去释放该内存,而其他的代码就不能 继续执行,没法去做别的事情了.但是可以将STW问题限制在1ms之内.

2.Java的垃圾回收要回收的内存是哪些?

JVM中有四个区域:堆区,方法区,栈区,程序计数器.堆区里面的内存主要是JVM需要释放的内心对象.而方法区里面的是类对象,它是类加载过来的,而方法区进行垃圾回收就相当于"类卸载".而栈区和程序计数器是跟进程绑定在一起的,在进程结束的时候,相应地栈区里面的变量和程序计数器就会随之自动释放内存空间了.

3.回收堆上的内存具体回收的是什么?

堆内存中,可以划分为:

垃圾回收机制 主要回收的就是完全不再使用的内存.对于一半在使用,一半不再使用的内存是不回收的,因为回收的成本比较大,当然实现起来也麻烦.

因此,java中的垃圾回收,是以对象为基本单位,一个对象,要么被回收,要么不被回收,不会出现一个对象被回收一半的情况.

4.如何找到垃圾 

如何找垃圾也可以称为(如何标记垃圾?/如何判定垃圾?).抛开java来说,单纯GC的话,判定垃圾有两种典型的方案.
1.引用计数
2.可达性分析

4.1 引用计数

引用计数,计就是通过一个变量来记录当前这个对象被几个引用来指向.一个对象就会内置一个计数器记录 它被几个变量所指向.

但是引用计数有一个致命的问题.当出现循环引用时

class Test {
   Test t = null;
}

Test t1 = new Test();//1
Test t2 = new Test();//2
t1.t = t2;//3
t2.t = t2;//4

t1=null;//3
t2=null;//2

当代码运行完t2=null的时候,按引用计数的情况来说,new Test()里面的计数器为2,但是此时内存是不再使用了,它不会被回收就会导致内存泄漏

缺点:空间利用率比较低(比较浪费空间,尤其是针对大量的小对象).本来引用次数就多,而且内置的计数器也占用空间(假如小于对象内存空间).
2.存在循环利用导致判定是否是垃圾出现了错误,从而无法回收.

5.2 可达性分析(java专用)

从一组初始位置出发,向下进行深度探索遍历,把所有能够访问到的对象都标记"可达性",对应地,没有访问到(不能访问)的对象就没有标记,没有标记的就是垃圾.

如:

JVM中采取的方案:在JVM中就存在一个/一组线程,来周期性地进行上述遍历的过程,不断地找出这些不可达的对象,由JVM进行回收.

可达性分析的初始位置有:
1.栈上的局部变量表中的引用.
2.常量池里面的引用指向的对象.
3.方法区中,引用类型的静态成员变量. 

基于上述过程就完成了对垃圾的标记,和引用计数相比,可达性确实麻烦,同时实现可达性分析的遍历过程开销是比较大的.但是带来的好处是解决了引用指针的两个缺点:内存上不需要消耗太多空间,也没有循环引用的问题

5.找到垃圾后如何回收

1.标记-回收
2.复制算法
3.标记-整理

5.1 标记回收

虽然可以释放掉不再使用的内存空间,但会导致内存碎片(内存不连续).如果申请一小块内存,没什么问题,如果申请一大块连续,此时可能会分配失败

5.2 复制算法

它是为了解决标记-清除的内存碎片问题.把内存分为两部分.把回收的内存进行回收,保留的内存复制到另一半内存区域中.
复制算法的缺点:
1.可用的内存空间,只有一半.
2.如果要回收的对象比较少,而剩下的对象比较多,复制内存的开销就很大

复制算法适用于对象会被快速回收,并且整体的内存不大的场景下

5.3 标记-整理

能够解决复制算法内存空间利用率的问题.类似于顺序表的删除的搬运操作.能够有效避免内存碎片,提供内存的利用率.
缺点:在搬运的过程中,是一个很大 的开销,这个开销可能比复制算法里面的开销更大

5.4 分代算法

分代回收的过程
1.一个新的对象,诞生于伊甸区.
2.如果活到一岁的对象(对象经历了一轮GC还没死),就拷贝到 生成区

生存区的内存大小比较小,那么空间小 能放的了这么多对象吗?
根据经验规律,伊甸区的对象,绝大部分活不过 一岁的,只有少数对象能够来到生存区.

3.在生存区中,对象也要经历若干轮GC,每一个GC留下来的对象都通过复制算法拷贝到另外的生存区.这里面的对象来回拷贝,每一轮都会淘汰掉一批对象.
4.在生存区中熬过一定轮次的GC之后,这个对象如果还没有被回收,JVM就认为这个对象未来能够更持久的存在下去,于是就将这样的对象拷贝到老年代了
5.进入老年区的对象,JVM都认为是属于能够持久存在的对象,这些对象也需要使用GC来扫描,但是扫描的频次大大降低.老年这里通常使用的标记-整理算法.

特殊地,如果一个对象的内存特别大,它会直接放入 老年区.因为如果到了生存区,在生存区的拷贝比较大,甚至有的对象太大在生存区放不下,直接放入老年区更合适.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值