https://www.cnblogs.com/jiangym/p/15885161.html

JVM内存模型(JMM)

根据代码画出下面的JVM内存模型

public class Math {

    public static final int initData = 666;
public static User user = new User(); public int compute(){
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
} public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
class User{ }

字节码class文件被“类装载子系统”,加载到运行时数据区,通过“字节码执行引擎”运行方法代码。

main方法开始运行后,就会有一个线程;每有一个线程,都会开辟一个线程特有的“虚拟机栈”(最左面),

compute()和main()在虚拟机栈里都会有一个自己的栈帧,方法对应的栈帧会在方法结束时被释放。

存栈帧的顺序也是按栈的先进后出来存和销毁。嵌套调用

(左上二图)局部变量,a,b都会放到局部变量里。

操作数栈里也是栈结构,栈顶会弹出ab然后操作,乘积为30然后放入操作数栈。在字节码文件执行时有程序计数器,计数器的数也是“字节码执行引擎”来控制的

在局部变量分配c的空间,然后从操作数里放到局部变量里。

User对象是静态的,在方法区存栈帧,指向的对象在堆中。

垃圾收集是“字节码执行引擎”开辟的一个线程,会根据可达性分析,以及三色标记算法,来处理。

分代年龄是在对象头里存,对象头第一个字宽(1字宽=4字节=32bit)存markword,(32位机)前25位是hashcode,之后四位是分代标识,

所以只能最多到1111即0-15次,就要去老年代。剩下三位与synchronized有关,存偏向锁和锁标志位。

三色标记算法

是为了在每次垃圾回收时,提升扫面效率,把已经扫过的节点忽略。

把每个对象分为黑灰白,未扫过的为白,扫过但是子节点未扫全的为灰,子节点都扫全的为黑。

扫描的线程有的时候会中断,会存在一个黑指向白的问题,导致下一次扫描的时候,白色扫不到,内存泄漏。

CMS解决的办法是,把这种场景的黑变为灰,但是还不能彻底解决这个问题,且扫完之后整体再扫一遍。

G1的解决办法是单独记录黑指向白的线,后续统一处理。

线程间通信

为提高性能,编译器和处理器常常会对指令重排序。

happens -before原则

//todo

垃圾收集器

CMS(老年代 oldGC)+parNew

一般CMS设置fullGC的阈值是内存到65%,如果等到内存满了会使用Serial-old,这个stw会很长时间

回收阶段

  1. 初始标记
    1. 标记根对象直接关联的对象 短暂stw
  2. 并发标记
    1. 找出所有与GCRoot能关联的对象,同时执行 不stw
  3. 并发预清理(重新标记)不一定执行
    1. 减少5工作量
  4. 并发可终止的预清理 不一定执行
  5. 重新标记
    1. 用户是并发的,怕期间修改,存在stw
  6. 并发清除
    1. 并发清除垃圾, 因为并发清除,所以不进行整理。
  7. 并发重置
    1. 清除CMS上下文信息

G1

把内存划分为若干个region,每个region在同一时刻,只属于一个代。

  • Eden
  • survivor
  • old
  • humongous 存大对象,连续的region

YongGC

mixedGC

这个是设计巧妙的部分,老年代占比到阈值(一般45%),会回收所有yongRegion同时回收部分oldRegion区的。

回收阶段

  1. 初始标记
    1. 标记根对象直接关联的对象 短暂stw
  2. 并发标记
    1. 找出所有与GCRoot能关联的对象,同时执行 不stw
  3. 最终标记 stw
  4. 筛选回收
    1. 根据用户期望,对回收成本进行排序,制定回收计划,并选择region回收

回收过程

region放到一个回收集(类似Set);把region存活对象放到空region里,删除region ---- 复制算法

fullGC

减少rullGC的办法:

  • 增加预留内存
  • 更早地垃圾回收
  • 增加并发阶段使用的线程数

>jdk8 一般都不用CMS了,用G1。

垃圾回收算法

  • 标记-清除

    • 存在内存碎片
  • 标记-整理
  • 复制
    • 2快内存,性能好,无碎片,内存利用率低。
  • 分代收集

可达性分析

对象是否有引用,可以用引用计数法和可达性分析,java使用可达性分析

引用计数法,引用+1,释放-1,来标记,存在循环引用的问题。

可达性分析:没有到gcroot的引用,即可回收。

finalize关键字

一旦垃圾回收器准备释放对象所占的内存空间, 如果对象覆盖了finalize()并且函数体内不能是空的, 就会首先调用对象的finalize(),  然后在下一次垃圾回收动作发生的时候真正收回对象所占的空间.

finalize代码块里加东西要慎重,避免内存泄漏。

最好使用try。catch。finally

类加载机制

类加载过程

类加载的时候是按需加载的

通过运行类时添加参数TraceClassLoading参数,获取类加载的时间线

1.首先读取rt的jar包

2.加载hello类

3.加载hello类的main方法里的uesr类

类加载过程(生命周期)

加载;(验证;准备;解析) => 链接;初始化;使用;卸载

  1. 加载:查找并加载类的二进制数据。
    1. (把类的.class文件的二进制数据读入内存,存放在运行时数据区的方法区;类加载的最终结果是产生 堆区中描述对应类的Class对象);
  2. 链接:包括验证、准备和解析三个子阶段;
    1. 验证:验证class文件的二进制是否符合规范
    2. 准备:在准备阶段就赋值了。
    3. 解析: 把符号引用翻译成直接引用。
  3. 初始化:给类中的静态变量赋予正确的初始值;
    1. 创建类(new、反射、克隆、反序列化)
    2. 使用静态方法、非静态变量
    3. Class.forName("ATest"); 获取描述类的Class对象;
    4. 类初始化顺序,是一个面试点
  4. 使用
  5. 卸载
    1. 场景:
    2. 该类所有实例都被GC
    3. 加载该类的ClassLoader已被GC
    4. 该类的Class对象没有被引用,也没有通过反射访问该类的方法。

  注意:使用能在编译期能得知的final static修饰的常量,不会导致类的初始化;
    public static final int a = 2*3;//编译时常量,不会导致类初始化;
    public static final int a b = (int)(Math.random()*10)/10+1; // 不是编译时常量,会初始化;
子类父类初始化过程:

先对这个类进行加载和连接-> 如果有直接父类,先加载连接初始化这个父类->重复以上步骤直到所有父类初始化,初始化当前类;
(先加载连接当前类,再加载连接初始化父类,再初始化当前类)

初始化阶段

变量给真正的值。把准备阶段给的默认值替换掉。

静态代码块执行。

创建对象时执行

普通代码块执行

构造器

类加载执行顺序

  1. 静态常量
  2. 静态变量
  3. 静态初始化块
  4. 变量
  5. 初始化块
  6. 构造器

有继承关系时,父子类加载顺序

  • 父类--静态变量
  • 父类--静态初始化块
  • 子类--静态变量
  • 子类--静态初始化块
  • 父类--变量
  • 父类--初始化块
  • 父类--构造器
  • 子类--变量
  • 子类--初始化块
  • 子类--构造器

类加载器classLoad

在类“加载”阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”,被成为“类加载器”

除了Java虚拟机自带的根类加载器以外,其余的类加载器有且只有一个父加载器;
Java虚拟机自带加载器

  • 根(Bootstrap)类加载器:没有父类加载器。负责加载虚拟机核心类。
  • 扩展(Extension)类加载器:父加载器为根加载器;继承于java.lang.ClassLoader;
  • 系统(System)类加载器:也称应用类加载器,父加载器为扩展类加载器;加载classpath路径下指定的类库;继承于java.lang.ClassLoader,也是自定义加载器的默认父类;

双亲委派

类加载过程中,
会先从最顶层加载器(一般是根加载器)开始往下,
先判断父类加载器能不能加载,能加载则往下传递返回加载的类;
不能加载则继续往下判断,如果都不能加载,则会抛出ClassNotFoundException异常;
加载器之间的父子关系实际上是指加载器对象之间的包装关系,而不是类之间的继承关系;

例如:

双亲委派加载例子

String类来加载;app传到Ext传到Boo,Boo发现rt下有类,就直接加载了;

ext下的类加载;app传到Ext传到Boo,Boo发现加载不了;返回下级加载器;Ext发现ext目录下有,就直接加载了;

用户定义User类加载;app传到Ext传到Boo,Boo发现加载不了;返回下级加载器;Ext发现加载不了,返回下级加载器;App加载器发现可以加载就加载了。

若其他类加载不到,则会抛ClassNotFoundException。

所以说双亲委派是对加载器来说的,总是往上层加载器抛,之前理解的不对,之前想的时子类和父类之间的加载关系。

好处

1.确保安全,避免核心类库被加载

2.避免重复加载

3.保证类的唯一性

例如自己写String类

程序会去RT的加载器加载,但是加载到之后发现main方法不存在,就会报错。

调优

执行 TOP 命令后

场景:CPU持续100%

常见原因

  1. 宿主机CPU超卖
  2. 内存问题 大量FUllGC
  3. 代码存在死循环

排查办法:

  1. 通过TOP命令可以查看到各个进程CPU,记录其PID
  2. 通过Jstat命令 打出垃圾收集日志,排查是否是内存溢出:
    1. Jstat -gcutil 12345 5000
    2. 查看YGC和FGC的次数和耗时。
    3. 如果是内存溢出,排查内存溢出位置。
    4. Jmap -dump :format=b,file = ../../heap.bin PID
    5. 用mat等工具查看
  3. 如果不是内存溢出,则有可能是存在死循环。排查死循环位置。
    1. Top -P 【pid】 -H
    2. 查看CPU占比高的子线程 1234
    3. 转成16进制 printf "%x/n" 1234 = abc
    4. 打印进程堆栈 查看对应abc的具体报错
    5. sudo -u admin/opt/java/bin/jstack 2066 > a.txt

cat proc/cupinfo 查看CPU的核数

负载高

CPU没满,但负载比较高

  等待磁盘IO的进程过多,进程队列过长。

常见场景:

  • 磁盘读写请求多
  • Mysql有不走索引的语句或死锁

内存异常

mem是内存使用情况

内存飙升常见场景:

  1. 内存溢出(堆、方法区、栈)
    1. 堆,fullGC也不起作用了
    2. 方法区:类加载过多
    3. 线程栈:递归太多,层级太深。
  2. 内存泄漏
    1. jmap -dump format=b file=heap.dump PID
    2. 用mat等分析

ThreadLocal 相关

java引用分为四种。强软弱虚四种引用。

强引用:

new 一个对象 是强引用

finalize()方法基本上不能重写,避免内存泄漏, 当一个对象被垃圾回收器clear的时候,会调用finalize

重写是为了跟踪回收过程。

System.gc() 建议gc,hotspots 会去回收

软引用:

SoftReference<> m = new SoftReference<>(new byte[1024*1024*10]);

加粗的部分为软引用

byte[] b = new byte[1024*1024*12];

这个是强引用,当执行这句话,空间不够时,上面的软引用会直接回收。

软引用适合缓存的使用,例如缓存图片。

弱引用:

遇到gc就被回收

虚引用:

NIO的零拷贝 这种情况  分配的直接内存 在JVM之外 不能直接被垃圾回收器回收

垃圾回收器里存个虚引用,回收时候有个队列,如果有相关需要删除的直接内存,则再释放。

ThreadLocal 

static ThreadLocal<Person> tl = new TheadLocal<>();

现象:

  一个线程set一个对象到tl里,另一个线程获取不到

原因要从tl.set()的源码入手:

  set是放到当前线程的threadLocalMap里了,所以本质是thread,person对象的一个键值对,放到当前线程的map里。

所以另一个线程获取不到上一个线程存放的数据。key不同。

应用:

spring的@transactional注解中,若有两个dao层M1和M2,为了保证M1获取的数据库链接和M相同,

吧M1的connection链接放到ThreadLocal里,M2从ThreadLocal里取,这样就保证了两次连接再一个事务里。

线程池里的线程如果用到ThreadLocal了,必须要手动清理掉,防止内存泄漏

threadLocalMap的key最初用的强引用,一直也回收不掉,越积累越多,后来编程弱引用了。

用不到当前threadLocal了,要调用tl.remove(); 底层是threadLocalMap.remove(当前线程);

相关资料

https://www.bilibili.com/video/BV14K411F7S4?p=1

https://www.bilibili.com/video/BV12b4y167Mb?p=2

https://www.bilibili.com/video/BV12b4y167Mb?p=12

https://www.bilibili.com/video/BV1s44y167Yj?from=search&seid=18316383145675461585&spm_id_from=333.337.0.0

《Java面象对象编程》

JVM相关总结的更多相关文章

  1. JVM相关参数配置和问题诊断<转>

    原文连接:http://blog.csdn.net/chjttony/article/details/6240457 1.Websphere JVM相关问题诊断: 由JVM引起的Websphere问题 ...

  2. 3.1日 重温JVM相关信息

    1.JDK.JRE.JVM的关系: JDK是java开发的必备工具箱,JDK其中有一部分是JRE,JRE是JAVA运行环境,JVM则是JRE最核心的部分. 2.JVM的组成: JVM由4大部分组成:C ...

  3. JVM相关知识

    Java虚拟机学习分享最近主要在学习JVM相关知识,-知识主要来源<深入理解JAVA虚拟机>,深有感触,结合自己的理解,整理出一些经验,由于篇幅较长,就把链接帖出来,希望对大家有所帮助: ...

  4. 了解java虚拟机—JVM相关参数设置(2)

    1.   JVM相关参数设置 JVM相关配置 -XX:+PrintGC 两次次YoungGC,两次FullGC. -XX:+PrintGCDetails 打印GC时的内存,并且在程序结束时打印堆内存使 ...

  5. 几个与JVM相关的JDK工具:jps, jstat, jmap

    在项目中遇到OOM(Out of Memory)的问题,为了分析内存和JVM的垃圾回收器GC问题,一并把JVM相关的一些工具也研究了一下: jps:Java进程查看工具,实际上它和Unix/Linux ...

  6. Java虚拟机JVM相关知识整理

    Java虚拟机JVM的作用: Java源文件(.java)通过编译器编译成.class文件,.class文件通过JVM中的解释器解释成特定机器上的机器代码,从而实现Java语言的跨平台. JVM的体系 ...

  7. JVM相关参数设置

    Java启动参数共分为三类: 其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容: 其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足, ...

  8. JVM相关 - 深入理解 System.gc()

    本文基于 Java 17-ea,但是相关设计在 Java 11 之后是大致一样的 我们经常在面试中询问 System.gc() 究竟会不会立刻触发 Full GC,网上也有很多人给出了答案,但是这些答 ...

  9. JVM相关问答

    大部分内容来源网络,整理一下,留个底. 问:堆和栈有什么区别? 答:堆是存放对象的,但是对象内的临时变量是存在栈内存中,如例子中的methodVar是在运行期存放到栈中的. 栈是跟随线程的,有线程就有 ...

  10. JVM相关知识(1)

    1.JVM内存管理的机制 内存空间划分为:Sun JDK在实现时遵照JVM规范,将内存空间划分为堆.JVM方法栈.方法区.本地方法栈.PC寄存器. 堆: 堆用于存储对象实例及数组值,可以认为Java中 ...

随机推荐

  1. 局部内部类定义-局部内部类的final问题

    局部内部类定义 定义格式: 修饰符 class 外部类名称 { 修饰符 返回值类型 外部类方法名(参数列表){ class 局部内部类名称{// ... } } } 小杰一下类的权限修饰符: publ ...

  2. 基于.NetCore开发博客项目 StarBlog - (26) 集成Swagger接口文档

    前言 这是StarBlog系列在2023年的第一篇更新~ 在之前的文章里,我们已经完成了部分接口的开发,接下来需要使用 curl.Postman 这类工具对这些接口进行测试,但接口一多,每次测试都要一 ...

  3. vscode 配置复盘

        第一句话,看文档!code.visualstudio.com/docs/editor- 从这里开始看,上下辐射看完debug看task,然后再看其他的诸如"智能感知"    ...

  4. 详解Redisson分布式限流的实现原理

    摘要:本文将详细介绍下RRateLimiter的具体使用方式.实现原理还有一些注意事项. 本文分享自华为云社区<详解Redisson分布式限流的实现原理>,作者: xindoo. 我们目前 ...

  5. C#中的Byte,String,Int,Hex之间的转换函数

    /// <summary> Convert a string of hex digits (ex: E4 CA B2) to a byte array. </summary> ...

  6. kali linux 使用教程

    kali linux使用教程 前言:Kali Linux 是专门用于渗透测试的linux操作系统,它由BackTrack发展而来,在整合了IWHAX.WHOPPIX和Auditor这三种渗透测试专用L ...

  7. echarts使用dataset数据集创建单轴散点图

    dataset创建单轴散点图 由于使用echarts作图时,我很喜欢用dataset作为数据源,但是官方案例中,有没有给出相关示例,于是,在翻阅官方文档相关案例之后,结合官方文档使用dataset的示 ...

  8. FCoE测试重启调试记录

    环境 CPU:Phytium,S2500/64 C00 内核版本:4.19.90-25.10 网讯网卡:txgbe 共两台设备,光纤直连 复现步骤 设备A.B分别执行以下操作,即可复现 modprob ...

  9. Nacos服务调用(基于Openfeign)

    在<<Nacos服务注册>>这篇文章里,我搭建了一个nacos服务中心,并且注册了一个服务,下面我们来看在上一篇文章的基础上,怎样用Openfeign来调用这个服务. 0.同上 ...

  10. 跳板攻击之:NPS代理转发

    跳板攻击之:NPS代理转发 目录 跳板攻击之:NPS代理转发 1 NPS介绍 2 NPS特点 3 实验环境 3.1 实验准备 3.2 实验拓扑 3.3 NPS配置 3.3.1 conf/nps.con ...