JVM基础

JVM基础

一、内存结构

 1、Program Counter Register 程序计数器 (寄存器)

     1.1、作用

       是记住下一条JVM指令的执行地址

     1.2、特点

       是线程私有的,不会存在内存溢出

 2、Java Virtual Machine Stacks(Java虚拟机栈)

     2.1、栈帧

        每个方法运行时需要的内存 (需要占用内存的比如参数,局部变量,返回地址)

     2.2、定义

        每个线程运行时所需要的内存,称为虚拟机栈

        每个栈由多个栈帧(Fram)组成,对应着每次方法调用时所占用的内存

        每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

     2.3、方法内的局部变量是否线程安全

        如果方法内部局部变量没有逃离方法的作用访问,它是线程安全的

        如果是局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全

     2.4、栈内存溢出(StackOverflowError)

        栈帧过多导致栈内存溢出

        栈帧过大导致栈内存溢出

        实例:递归没有终止条件,JSON 数据转换(部门包含人员,人员包含部门)用@JsonIgnore解决

     2.5、线程运行诊断

        top 动态地持续监听进程地运行状态

        ps -H -eo [pid,tid,%cpu] 查看线程 [ ] 内的目标指标

        ps -H -eo [pid,tid,%cpu] | grep pid值 可以过滤掉不感兴趣的进程

        jstack 进程id 可以根据进程id 找到有问题的线程,进一步定位到有问题代码的源码行号

 3、Heap 堆

     3.1、定义

        通过new关键字,创建对象都会使用堆内存

     3.2、特点

        它是线程共享的,堆中对象都需要考虑线程安全的问题

        有垃圾回收机制

     3.3、堆内存溢出

     3.4、堆内存诊断

        jps 查看当前系统中有哪些java进程

        jmap 查看堆内存占用情况(jmap -heap 进程号)

        jconsole图形界面的,多功能监测工具,可以连续监测

     3.5、垃圾回收后,内存占用仍然很高如何处理

        Java VisualVM -> 监视 ->堆Dump

 4、Method Area 方法区

     4.1、定义

       
在这里插入图片描述

     4.2、内存溢出

      1.8以前会导致永久代内存溢出
       java.lang.OutOfMemoryError :permGen space
      -XX:MaxPermSize=8m

      1.8以前会导致元空间内存溢出
       java.lang.OutOfMemoryError :Metaspace
      -XX:MaxMetaspaceSize=8m

      场景 Spring Mybatis

     4.3、运行时常量池

       常量池、就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息。

       运行时常量池,常量池是*.class文件中的,当该类被加载,他的常量信息就会放入运行时常量池,并把里面的符号地址变为真实地址

       查看.class文件内容

       javap、idea插件:jclasslib

     4.4、StringTable特性

       常量池中的字符串仅是符号,第一次用时才变为对象

       利用串池的机制,来避免重复创建字符串对象

       字符串变量拼接的原理是StringBuilder(1.8)

       字符串常量拼接的原理是编译期优化

       可以用intern方法,主动将串池中还没有的字符串对象放入串池

       1.8串池用的堆空间,1.6串池用的永久代

     4.5、StringTable垃圾回收机制

      部分参数 -Xmx10m -XX:+printStringTableStatistics -XX:+PrintGCDetails -verbose:gc

     4.6、StringTable性能调优

      调整桶个数使哈西查找变快-XX:StringTableSize= [1009-2305843009213693951]

      考虑将字符串对象是否入池 如果存在大量字符串/重复字符串,可以入池减少堆内存的使用

 5、直接内存

     5.1、定义

      常见于NIO操作,用于数据缓冲区

       分配回收成本高,但读写性能高

       不受JVM内存回收管理

       直接内存的回收通过unsafe进行回收

     5.2、分配和回收原理

      使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法

      ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

二、类加载器

理解类加载器
https://blog.csdn.net/m0_50988781/article/details/120998812

三、java的编译模式

  1、解释器

  2、JIT

  3、混合模式

混合使用解释器+热点代码编译

起始阶段采用解释执行

热点代码监测

-Xmixed 默认为混合模式,开始解释执行,启动速度快

-Xint 使用纯解释模式,启动很快,执行稍慢

-Xcomp 使用纯编译模式,启动很慢,执行很快

四、硬件层数据一致性

  1、存储器的层次结构

在这里插入图片描述

  2、多线程一致性的硬件层支持

    2.1、老CPU:总线锁会锁住总线,使得其他CUP甚至不能访问内存中其他的地址,因而效率较低

 2.2、新CPU:数据一致性实现 = 缓存锁(各种各样的一致性协议) + 总线锁

 MESI Cache一致性协议

 缓存锁实现之一,有些无法被缓存的数据或者跨越多个缓存行的数据依然必须使用总线索

 2.3、读取缓存以cache line为基本单位 目前64byte

 3、伪共享

  位于同一缓存行(cache line 即64字节数组 )的两个不同数据,被两个不同CPU锁定,产生互相影响的伪共享问题

  解决方案使用缓存行的对齐

    //通过对缓存行的对齐避免混入其他的缓存行来提高效率。
    private static class Padding {
        public volatile long p1,p2,p3,p4,p5,p6,p7;
    }
    private static class T extends Padding{
        public volatile long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() ->{
            for (long i = 0; i < 1000_1000L; i++) {
                arr[0].x = i;
            }
        });
        Thread t2 = new Thread(() ->{
            for (long i = 0; i < 1000_1000L; i++) {
                arr[1].x = i;
            }
        });
        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime()-start)/100_0000);
    }

disruptor 样例如下

在这里插入图片描述

五、乱序(执行顺序)

1、CPU为了提高指令执行效率,会在一条指令执行过程中(比如去内存堵数据(慢100倍)),去同时执行另一条指令,前提是,两条指令没有依赖关系。读操作也可进行合并。

2、有序性保障

 2.1、加锁

 2.2、加内存屏障

  sfence:在sfence指令前的写操作必须在sfence指令后的写操作前完成

  Ifence:在Ifence指令前的读操作必须在Ifence指令后的读操作前完成

  mfence:在mfence指令前的读写操作必须在mfence指令后的读写操作前完成

 2.3、Intel lock汇编指令

 2.4、JMM模型与内存屏障

在这里插入图片描述

六、垃圾回收

 1、如何判断对象可以回收

  1.1、引用计数法

   弊端A对象与B对象互相引用,计数都为1,都不会被使用但还是不可被回收,造成内存泄漏

  1.2、可达性分析算法

   Java虚拟机中的垃圾回收器采用可达性分析探索所有存活的对象

   扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收

   GC Root对象

  1.3、四种引用

   1、强引用

   2、软引用

    垃圾回收一次后,内存还不够会将软引用的对象回收掉(无其他强引用引用)

    若创建软引用的时候被分配了引用队列,当软引用对象被回收后,则会进入引用队列

    软引用应用样例

//-Xmx20M -XX:+PrintGCDetails -verbose:gc
public class Demo {

    private static final int _4MB = 1024*1024*4;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        //引用队列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {
            //关联了引用队列,当软引用所关联的byte[]被回收时,软引用自己会加入到queue中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        //从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while(poll != null){
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("=============================");

        for(SoftReference<byte[]> reference:list){
            System.out.println(reference.get());
        }

    }

}

   3、弱引用

    垃圾回收会将弱引用对象回收掉(无强引用对象引用)

    若创建弱引用的时候被分配了引用队列,当弱引用对象被回收后,则会进入引用队列

//-Xmx20M -XX:+PrintGCDetails -verbose:gc
public class Demo {

    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        // list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();
        }
        System.out.println(list.size());
    }

}

   4、虚引用

    必须配合引用队列使用

    结合直接内存篇学习,虚引用Cleaner

   5、终结器引用

    必须配合引用队列使用

    finallize()被垃圾回收时加入引用队列,然后由优先级很低的线程查看引用队列是否有终结器引用,根据终结器引用找到finallize()的对象调用finallize()方法,然后回收掉
在这里插入图片描述

 2、堆内存逻辑分区

  new(新生代) 包括eden SurvivorFrom SurvivorTo 占比8 1 1

  old (老年代) 包括tenured

  对象首先分配在伊甸园区

  新生代空间不足,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1,并且交换from to
  minor gc 会引发 stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行
  当对象寿命超过阈值时,会晋升至老年代,最大寿命15(4bit)
  当老年代空间不足,会先尝试minor gc,如果minor gc空间仍然不足,则full gc(STW时间更长)

 3、相关JVM参数

在这里插入图片描述

七、GC

java -XX:+PrintCommandLineFlags -version可以查看垃圾回收器的使用

 1、垃圾回收算法

  1.1、Root Searching(根可达算法)

  1.2、Mark-Sweep(标记清除)

  1.3、Copying(拷贝)

  1.4、Mark-Compact(标记压缩)

 2、常用垃圾回收器

  2.1、内存分带模型

   1、Serial

    针对新生代;

    采用复制算法;

    单线程收集;

    进行垃圾收集时,必须暂停所有工作线程,直到完成;

    “Stop The World”;(STW)

   2、Serial Old

    针对老年代;

    采用标记清除/标记压缩;

   3、ParallelScavenge

    又称为吞吐量优先收集器,和ParNew收集器类似,是一个新生代收集器。使用复制算法的并行多线程收集器。Parallel Scavenge是Java1.8默认的收集器,特点是并行的多线程回收,以吞吐量优先。

   4、ParallelScavenge Old

    是Parallel Scavenge收集器的老年代版本,用于老年代的垃圾回收,但与Parallel Scavenge不同的是,它使用的是“标记-整理算法”。适用于注重于吞吐量及CPU资源敏感的场合。

   Concurrent

    5、ParNew(Serial的多线程版本)配合CMS使用

     针对新生代;

     采用复制算法;

     多线程收集;

     进行垃圾收集时,必须暂停所有工作线程,直到完成;

     “Stop The World”;(STW)

     6、CMS (1.4诞生、1.9取消);

       并发执行 (并发标记,容易产生漏标) 所以CMS的remark阶段,必须从头扫描一遍;

       cms只会回收老年代和永久带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻带;

       cms是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以cms垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%;

  2.2、内存不分带模型

   1、G1

    采用Region即分区算法(部分回收)

    FGC需要连续区域放入Humongous,如果连续不上开始整理

    一次回收要把年轻代YGC全部回收(如果年轻代大非常慢所以诞生了ZGC)

    物理不分代,逻辑分代

   2、ZGC

    Colored Pointer(颜色指针) + Load Barrier(读屏障)

    取消年轻代、老年代

    分区(2的倍数:1、2、4、8、32)

   3、Shenandoah(JDK12 红帽开发)

    与ZGC 类似 Shenandoah有3个屏障

  2.3、Epsilon(JDK11、什么都不干垃圾回收器)

   1、用途

    开发JVM团队 DEBUG用

    程序特别大用不上,直接用Epsilon整个消失,效率极高

 3、三色标记算法

  3.1、三色标记

   黑色:当下一次垃圾回收再来的时候黑色的就不再扫描了,即自身标记完,fields都标记完成

   灰色:当下一次垃圾回收再来的时候灰色的就不再扫描自己,只扫描fields

   白色:没有遍历到的节点

  3.2、B->D消失A->D增加

   解决方案(CMS方案Incremental Update):A是黑色指向新对象的时候,A变成灰色
在这里插入图片描述

 4、GC调优

  4.1、什么是调优

   1、根据需求进行JVM规划和预调优

   2、优化运行JVM运行环境(慢、卡顿) (怎么才能定位一个系统的瓶颈、压测)

   3、解决JVM运行过程中出现的各种问题(Memory Leak OOM)

  4.2、调优实例

   1、如何在IDEA中设置JVM参数

    IDEA --> run --> Edit Configurations --> VM options

   2、JVM 参数设置 -Xms200M -Xmx200M -Dfile.encoding=UTF-8 -XX:+PrintGC URL

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class JVMTest {
    private static class CardInfo {
        BigDecimal price = new BigDecimal(0.0);
        String name = "张三";
        int age = 5;
        Date Birthdate = new Date();

        public void m(){};
    }

    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) throws InterruptedException {
        executor.setMaximumPoolSize(50);
        for (;;){
            modelFit();
            Thread.sleep(100);
        }
    }
    private static void modelFit (){
        List<CardInfo> taskList = getAllCardInfo();
        taskList.forEach(info -> {
            executor.scheduleWithFixedDelay(()->{
                info.m();
            },2,3, TimeUnit.SECONDS);
        });
    }

    private static List<CardInfo> getAllCardInfo(){
        List<CardInfo> taskList = new ArrayList<>();
        for (int i = 0; i < 100 ; i++) {
            CardInfo ci = new CardInfo();
            taskList.add(ci);
        }
        return taskList;
    }
}

在这里插入图片描述
在这里插入图片描述

八、G1

九、JVM内存划分

大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) , VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack ( 本地方法栈 ),其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。

首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值