jvm核心技术梳理(持续更新)

目录

栈上分配

逃逸分析

标量替换

内存溢出和内存泄露区别

跨平台原理

JVM的运行机制

编译原理

冯诺依曼模型

类文件Class结构

 对象的创建

对象的结构

内存分配策略

类加载器

类加载过程

双亲委派机制

对象的访问

什么是安全点

编译型和解释性

什么时候垃圾回收

实例化一个对象的过程

大端存储和小端存储

loadClass和forName的区别

-Xms和-Xmx为什么设置一样

tomcat怎么设置jvm参数

调优参数

各版本垃圾收集器

JVM性能调优与底层原理分析(学习笔记)

JVM内存模型

判断垃圾以及回收垃圾和四大引用

四种垃圾收集算法和垃圾收集器记忆技巧

深入理解JVM-ZGC垃圾收集器

full gc过于频繁该怎么解决

怎么合理设置JVM内存分配的比例

CMS与三色标记算法

使用JProfiler分析dump文件定位OOM

使用JVisualVM分析dump文件定位OOM

JVM常用命令汇总

JVM的GC日志分析

java获取jvm是32位或64位

System.gc()和Runtime.getRuntime().gc()区别

栈上分配

栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。

逃逸分析

逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。

标量替换

内存溢出和内存泄露区别

内存溢出(out of memory):指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但它存了long才能存下的数,那就是内存溢出。
内存泄露(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
 例子如下:
  内存溢出,就是说,你向系统申请了装10个橘子的篮子(内存)并拿到了,但你却用它来装10个苹果,从而超出其最大能够容纳的范围,于是产生溢出;内存泄漏,就是说系统的篮子(内存)是有限的,而你申请了一个篮子,拿到之后没有归还(忘记还了或是丢了),于是造成一次内存泄漏。在你需要用篮子的时候,又去申请,如此反复,最终系统的篮子无法满足你的需求,最终会由内存泄漏造成内存溢出。

跨平台原理

所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。
实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。

JVM的运行机制

.java--javac编译-->.class->类加载子系统->运行时数据区->执行引擎(即时编译器/垃圾回收器)->本地接口库->本地方法库

-本地接口库调用本地方法库完成具体的指令操作。
-执行引擎:任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM 中的执行引擎充当了将高级语言翻译为机器语言的译者。
-本地接口库:作用是融合不同的编程语言为 Java 所用。
-本地方法库:存储的是本地方法的实现方法。

JIT是编译器和解释器的组合,编译器和解释器的工作都是将程序员的源代码翻译成可执行的机器代码,要么一次性翻译(编译器),要么逐行解释并运行(解释器)。编译器可以扮演一个很好的翻译器,因为它可以使代码快速运行;然而,它必须先将源代码翻译成二进制文件,然后才能执行。当我们只有机器代码工作时,从源代码中调试一些问题就很痛苦。另一方面,解释器可以在运行时直接执行代码片段,这意味着如果有问题,它可以在运行时保存执行代码被调用的上下文(context)。然而,解释器多次重新翻译代码,这使它变得缓慢和低效。那么,JIT 在这方面的作用是什么呢?首先,JIT 像它的其中一位父母——意思是,开始的时候像解释器(Interpreter),在被调用的时候执行并重复运行代码。然而,如果 JIT 发现代码被多次调用并循环引用,它的行为就像另外一位父母:编译器(Compiler)。JIT 的行为像解释器(Interpreter),直到它注意到正在做一堆重复的工作。这点上,它更像编译器(Compiler),并将通过直接编译来优化重复代码。这使得 JIT 能够从它的父母中吸取精华。虽然它是从解释源文本开始的,但是它以一种特殊的方式进行。JIT 必须在解释的过程中仔细观察它所运行的内联代码。

编译原理

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

冯诺依曼模型

输入设备/输出设备/存储器/运算器/控制器 

类文件Class结构

ClassFile {
    u4             magic; //Class 文件的标志
    u2             minor_version;//Class 的小版本号
    u2             major_version;//Class 的大版本号
    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的访问标记
    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口
    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有个字段
    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法
    u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合
}

 对象的创建

类加载检查

虚拟机遇到一条new指令时,先根据new的参数在常量池中定位一个符号引用,没有找到引用,则执行的类的加载、验证、初始化

 分配内存

指针碰撞:
用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,分配内存的时候只需要向着没用过的内存方向将该指针移动对象内存大小位置即可.
空闲列表:
虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块足够大的内存块来划分给对象实例,最后更新列表记录.

内存分配并发问题

CAS+失败重试:
CAS是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
TLAB:
为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配

初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值 

 设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式

执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 

对象的结构

对象头(Header):
1.markword:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit
2.klass:klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
3.数组长度(只有数组对象有):如果对象是一个数组,那在对象头中还必须有一块数据用于记录数组长度
实例数据:
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
对齐填充:
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpotVM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍)因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 

内存分配策略

-对象优先分配在Eden区:大多数情况下,对象在新生代中的Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发生一次Minor GC。
-大对象直接进入老年代:大对象是指,需要大量连续内存空间的Java对象,虚拟机提供了相关参数调整大小。
-长期存活的对象进入老年代:每次Minor GC,年龄就增加一岁,默认15岁,进入老年代,也可以通过参数调整。
-动态对象年龄判定:如果在Survivor空间中相同年龄所有对象大小的总和大小大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
-空间分配担保:在发生Minor GC前,虚拟机会检查老年代的最大可用连续空间是否大于新生代所有对象的总空间,如果大于,则此次Minor GC是安全的.如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。

类加载器

类加载器负责加载class文件加载到JVM内存
Bootstrap ClassLoader(启动类加载器C++)负责加载$JAVA_HOME/jre/lib/rt.jar下面的类
Extension ClassLoader(扩展类加载器Java)负责加载$JAVA_HOME/jre/lib/ext/*.jar下面的类
System ClassLoader(系统类加载器Java)负责加载当前应用classpath下面所有类

隐式加载:使用new +构造方法时,隐式的调用类加载器,加载对应的类到JVM中,是最常见的类加载方式

显式加载:使用loadClass()、forName()等方法显式的加载需要的类,对于显式加载这种类加载方式来讲, 当我们获取到了Class对象后,需要调用Class对象的newInstance()方法来生成对象的实例

手写自己的类加载器

类加载过程

 加载:
读取class文件并把class文件描述成Class对象的过程。
1.根据类的全限定名获取类的二进制字节流
2.二进制字节流代表的静态结构转化为方法区运行时的数据结构
3.内存创建Class对象,作为方法区这个类的各种访问入口
验证:
验证Class文件是否符合虚拟机的规范
准备:
方法区中的类变量赋初值和分配内存空间
解析:
JVM把常量池内的符号引用转为直接引用
初始化:
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10.
必须初始化
1.必须初始化(new,getstatic,putstatic,invokestatic)
2.反射reflect包的使用
3.初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4.当虚拟机启动时,用户需要定义一个要执行的主类,虚拟机会先初始化这个类。
5.MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用,就必须先使用findStaticVarHandle来初始化要调用的类。

双亲委派机制

 其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己才想办法去完成。
双亲委派机制的优势:
1.避免类的重复加载
2.防止核心API库被随意篡改

缓存机制:对于一个类加载器实例来说,相同全名的类只加载一次,即loadClass方法不会被重复调用
打破双亲委派:SPI/OSGI 

对象的访问

 使用句柄:
如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息

直接指针:
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象的地址。 

什么是安全点

-分析过程中对象引用关系不会发生变化的点
-产品Safepoint的地方:方法调用;循环跳转;异常跳转;
-安全点数量得中

编译型和解释性

编译型:使用专门的编译器,针对特定的平台,将高级语言源代码一次性的编译成可被该平台硬件执行的机器码,并包装成该      平台所能识别的可执行性程序的格式(C、C++、GoLang)
解释性:使用专门的解释器对源程序逐行解释成特定平台的机器码并立即执行。是代码在执行时才被解释器一行行动态翻译       和执行,而不是在执行之前就完成翻译
Java属于编译型+解释型的高级语言

什么时候垃圾回收

1.当Eden区或者S区不够用了
2.老年代空间不够用了
3.方法区空间不够用了
4.System.gc()

实例化一个对象的过程

1.分配内存空间
2.初始化对象
3.将内存空间的地址赋值给对应的引用

大端存储和小端存储

小端存储:便于数据之间的类型转换,例如:long类型转换为int类型时,高地址部分的数据可以直接截掉。
大端存储:便于数据类型的符号判断,因为最低地址位数据即为符号位,可以直接判断数据的正负号

 验证java是大端还是小端

loadClass和forName的区别

-使用loadClass()方法获得的Class对象只完成了类加载过程中的第一步:加载,后续的操作均未进行
-使用Class.forName()方法获得Class对象是已经执行完初始化的了

-Xms和-Xmx为什么设置一样

内存空间扩容时会发生内存抖动,影响程序的稳定性,不一样的时候会频繁的触发GC(为了避免在生产环境由于heap内存扩大或缩小导致应用停顿,降低延迟,同时避免每次垃圾回收完成后JVM重新内存分配。所以,-Xmx和-Xms一般都是设置相等的)

tomcat怎么设置jvm参数

添加JVM参数到Tomcat的bin目录下,打开文件catalina.bat,添加如下参数然后保存。set'JAVA_OPTS'

调优参数

-Xms2g:初始化推大小为2g;
-Xmx2g:堆最大内存为2g;
-Xss:规定了每个线程虚拟机栈(堆栈)的大小
-XX:NewRatio=4:设置年轻代和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代Eden和Survivor比例为8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用ParNew + ParNew Old垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-Xmn:新生代的初始大小(-XX:NewSize)
-XX:PermSize:永久代的初始大小
-XX:MaxPermSize:永久代的最大大小
-XX:+PrintGC:开启打印gc信息;
-XX:+PrintGCDetails:打印gc详细信息

各版本垃圾收集器

-jdk1.7默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
-jdk1.8默认(-XX:UseParallelGC)垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
-JDK1.9默认采用的就是G1垃圾回收器
JDK1.8新特性:
速度更快 – 红黑树
代码更少 – Lambda
强大的Stream API – Stream
便于并行 – Parallel
最大化减少空指针异常 – Optional

JVM性能调优与底层原理分析(学习笔记)

https://blog.csdn.net/xiaowanzi_zj/article/details/120837263

JVM内存模型

https://blog.csdn.net/xiaowanzi_zj/article/details/116105758

判断垃圾以及回收垃圾和四大引用

https://blog.csdn.net/xiaowanzi_zj/article/details/116110943

四种垃圾收集算法和垃圾收集器记忆技巧

https://blog.csdn.net/xiaowanzi_zj/article/details/116111882

深入理解JVM-ZGC垃圾收集器

https://blog.csdn.net/xiaowanzi_zj/article/details/116905022

full gc过于频繁该怎么解决

https://blog.csdn.net/xiaowanzi_zj/article/details/117447235

怎么合理设置JVM内存分配的比例

https://blog.csdn.net/xiaowanzi_zj/article/details/121506749

CMS与三色标记算法

https://blog.csdn.net/xiaowanzi_zj/article/details/121738422

使用JProfiler分析dump文件定位OOM

https://blog.csdn.net/xiaowanzi_zj/article/details/122335429

使用JVisualVM分析dump文件定位OOM

https://blog.csdn.net/xiaowanzi_zj/article/details/122592982

JVM常用命令汇总

https://blog.csdn.net/xiaowanzi_zj/article/details/122422764

JVM的GC日志分析

https://blog.csdn.net/xiaowanzi_zj/article/details/122998876

java获取jvm是32位或64位

第一种:System.getProperty("sun.arch.data.model")
第二种:控制台 java -version
第三种:控制台 java -d32 -version/java -d64 -version

System.gc()和Runtime.getRuntime().gc()区别

https://blog.csdn.net/xiaowanzi_zj/article/details/123910758

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      
Tomcat作为一款非常流行的Java Web服务器容器,在使用过程中难免会出现各种异常。下面是一些常见的Tomcat异常及其解决办法: 1. java.net.BindException: Address already in use: JVM_Bind 该异常表示端口已被占用。可能是由于上次Tomcat没有正常关闭,导致端口没有被释放。可以通过命令行查看并杀死占用端口的进程,或者修改Tomcat的配置文件指定其他端口。 2. java.lang.OutOfMemoryError: PermGen space 该异常表示永久代空间不足。可以通过增加Tomcat的JVM内存限制或者调整JVM的GC策略来解决。 3. java.lang.OutOfMemoryError: Java heap space 该异常表示堆内存不足。可以通过增加Tomcat的JVM内存限制或者调整JVM的GC策略来解决。 4. org.apache.catalina.core.StandardWrapperValve invoke 严重: Servlet.service() for servlet 抛出异常 该异常表示Servlet在处理请求时出现了异常。可以查看Tomcat日志文件或者应用程序代码来定位问题并解决。 5. java.lang.NoClassDefFoundError 该异常表示类文件没有找到。可能是由于类路径配置错误或者缺少依赖库。可以检查类路径配置和依赖库是否正确。 6. java.lang.UnsupportedClassVersionError 该异常表示类文件的版本不受支持。可能是由于编译Java代码使用了高版本的JDK,而Tomcat使用的JDK版本过低。可以升级Tomcat的JDK版本或者重新编译Java代码。 7. java.lang.IllegalStateException: Cannot forward after response has been committed 该异常表示响应已经被提交,无法进行转发。可能是由于在响应已经提交后仍然尝试进行转发。可以检查代码逻辑并确保在响应提交后不再进行转发。 以上是一些常见的Tomcat异常及其解决办法,但并不是所有的异常都在此列出。在遇到其他异常时,可以通过查看Tomcat日志文件和调试代码来定位问题并解决。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡^泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值