JVM

JVM体系结构

1.JVM的位置

在这里插入图片描述

2.JVM的体系结构

在这里插入图片描述

3.类加载器

含义
将class文件通过二进制读到内存,并且放在运行时数据区内的方法区内,然后在堆区创建class对象,用于存放数据。
分类

  1. 虚拟机自带加载器
  2. 启动类(根)加载器
  3. 扩展类加载器
  4. 应用程序加载器
    双亲委派模型
    含义:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
    1.类加载器收到类加载的请求
    2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
    3.启动加载器检查是否能够加载当前这个类,能就结束,不能就抛出异常,通知子加载进行加载。
    好处:避免重复加载 + 避免核心类篡改
    注意:我们一般不能自己写个类叫java.lang.System
    |解释:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。|
protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查这个classsh是否已经加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // c==null表示没有加载,如果有父类的加载器则让父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果父类的加载器为空 则说明递归到bootStrapClassloader了
                        //bootStrapClassloader比较特殊无法通过get获取
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {}
                if (c == null) {
                    //如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

4.沙箱安全机制
沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
*

5.native关键字*

凡是带了native关键字的,说明Java的作用范围达不到了,会去调用底层的c语言的库。,会进入本地方法栈,然后再调用本地接口(JNI),本地接口作用:扩展Java的使用,融合不同的编程语言为Java语言所用。
Java在内存区域专门开辟一块标记区域:native method stack,登记native方法。

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

        },"my thread name").start();
    }
    private native  void start0();
   // public void start();//这里报错,因为没有native
}

6.pc寄存器*

7.方法区

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,
简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关I

方法区就存static,final,class,常量池这些。
下面就是对象在类加载时的内存分析:
在这里插入图片描述

8.栈

栈:先进后出,后进先出:桶
队列:先进先出(FIFO)
思考:为什么main方法先执行最后结束
栈:栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收
一旦线程结束,栈就结束
栈主要存储:8大基本类型,对象引用,实例方法
在这里插入图片描述
栈满了:stackovererror
栈+堆+方法区:的交互关系
在这里插入图片描述
对象在内存中的实例化过程:
在这里插入图片描述

9.堆

Heap,一个JVM只有一个堆内存,堆内存大小是可以调节的
类加载器读取了类文件后,一般会把类,方法,常量,变量放到堆中。保存我们的真实对象。
堆内存中还要细分为三个区:

  • 新生区:Eden space(所有对象都是在这new出来的),幸存区
  • 养老区:
  • 永久区:用来接收JDK自身携带的class对象
  • jdk1.6之前:永久代,常量池是在方法区
  • jdk1.7:慢慢退货永久代,常量池在堆中
  • jdk1.8:无永久代,常量池在元空间
    在这里插入图片描述
    假设内存满了抛出outofMemoryError
    在jdk8以后,永久存储区改了名字为元空间
    在这里插入图片描述
    在一个项目中出现OOM故障,那么该如何排除,为什么出错
  • 能看到代码第几行出错:内存快照分析工具,MAT,Jprofiler
  • Debug,一行行分析代码
  • MAT,Jprofiler作用
    1.分析Dump内存文件,快速定位内存泄漏,获取堆中数据,获取最大对象
    .堆结构
    分为新生代和老年代,JDK1.8中,永久代已经从java堆中移除;
    所有新生成的对象首先都是放在年轻代的(除了部分大对象通过内存担保机制创建到Old区域,默认大对象都是能够存活较长时间的);
    年轻代分三个区。一个Eden区,两个 Survivor区(一般而言),可以配置多个,他们之间的比例为8:1:1,也可调节;
    当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区;
    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中;
    Eden:该区域的对象大部分都是短时间都会死亡的,故垃圾回收器针对该部分主要采用标记整理算法了回收该区域。
    Survivor:复制算法
    Old: 标记清除

GC:垃圾回收

在这里插入图片描述

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

  • 新生代
  • 幸存区(from ,to)
  • 老年区
    GC两种类型:轻GC,重GC

JVM的内存模型和分区?

在这里插入图片描述

堆里面的分区有哪些?他们的特点

堆里面分为新生代和老生代(java8取消了永久代,采用了Metaspace),新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to,当经过一次或者多次GC之后,存活下来的对象会被移动到老年区,当JVM内存不够用的时候,会触发Full GC,清理JVM老年区
当新生区满了之后会触发YGC,先把存活的对象放到其中一个Survice
区,然后进行垃圾清理。因为如果仅仅清理需要删除的对象,这样会导致内存碎片,因此一般会把Eden 进行完全的清理,然后整理内存。那么下次GC 的时候,就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下,就会使用老年代的担保,直接放到老年代里面。因为JVM 认为,一般大对象的存活时间一般比较久远。

GC的算法有哪些和怎么用

标记清除算法:先标记需要清除的对象,标记完之后清除对象;
好处:不需要移动对象,效率高,在对象存活率较高的情况下效率非常高;
缺点:可用的内存并不是连续的,而是断断续续,造成大量的内存碎片。 存储对象时要求内存空间时连续的,所以虚拟机在给新的内存较大的对象分配空间时,有可能找不到足够大的连续的空闲的空间来存放,从而引发一次垃圾回收动作;
标记整理算法:先标记需要清除的对象,然后将所有存活的对象向一端移动;
好处:内存空间连续性大;
缺点:需要移动对象。造成时间、cpu的浪费 ;
复制算法:将内存分为两块大小一样的区域,每次是使用其中的一块。当这块内存块用完了,就将这块内存中还存活的对象复制到另一块内存中,然后清空这块内存;
好处:在对象存活率低时效率很高(例如新生代,因为新生代的对象基本上都是朝生夕死的,存活下来的对象约占10%左右),在垃圾回收的过程也不会出现内存碎片的情况,不需要移动对象,只需要移动指针即可,实现简单,所以运行效率很高;
缺点:每次只能使用一半的内存;

虚拟机如何判断哪些对象需要进行GC处理?

java虚拟机是通过可达性分析算法来判定对象是否存活。当一个对象到GC Roots没有任何引用链相连,或者说从GC Roots到这个对象不可达时,这个对象将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象

轻GC和重GC分别在什么时候发生

年青代的eden区满了会触发younggc
老年代old满了会触发oldgc
Full GC清理整个heap区,包括Yong区和old区

引用计数器:
在这里插入图片描述
复制算法;
好处:没有内存碎片
坏处:浪费内存空间,只能用一半
最佳使用场景:对象存活率较低的新生区
在这里插入图片描述
在这里插入图片描述
标记清除算法:
好处:不需要额外的空间
缺点:两次扫描,严重浪费空间
在这里插入图片描述
标记压缩算法:(对上面优化)
在这里插入图片描述
内存效率:复制算法>标记清除>标记压缩
内存整齐度:复制算法=标记压缩>标记清楚
内存利用率:标记压缩算法=标记清除算法>复制算法
年轻代:

  • 存活低
  • 复制算法
    老年代:
  • 区域大:存活率
  • 标记清除(内存碎片不是很多时)+标记压缩

JMM(Java内存模型)

JMM:(Java Memory Model的缩写)//Java内存模型的缩写
作用:
缓存一致性协议,用于定义数据读写规则
JMM定义线程工作内存和主存之间的抽象关系:线程之间的共享变量存储在主存中,每个线程都有一个私有的本地内存

在这里插入图片描述
解决共享对象可见性的这个问题:volitile
volatile:保证可见性和有序性,不保证原子性

JMM指定的规则

。■不允许read和load. store和write操作之- -单独出现。即使用了read必须load, 使用了store必须
write
■不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
■不允许-一个线程将没有assign的数据从工作内存同步回主内存
■一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实
施use. store操作之前, 必须经过assign和load操作
■一个变量同-时间只有一个线程能对其进行lock,多次lock后,必须执行相同次数的unlock才能解锁
■如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必
须重新load或assign操作初始化变量的值
■如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock- 个被其他线程锁住的变量
■对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对yolatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安
全的了。但是这些规则实在复杂,很难在实践中直接分析.所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值