rocketmq JVM/OS配置

1.6.1.1.1. Oracle jdk 与 open 关系

image-20221006183019452

Java 跨平台语言、jvm跨语言平台

1.6.1.1.2. java发展史

  • 2000年,JDK 1.3发布,Java Hot Spot Virtual Machine正式发布,成为Java的默认虚拟机。
  • 2002年,JDK 1.4发布,古老的C1assic虚拟机退出历史舞台。
  • 2003年年底,Java平台的Scala正式发布,同年Groovy也加入了 Java阵营。
  • 2006年,JDK 6发布。同年,Java开源并建立了 OpenJDK。顺理成章,Hotspot虚拟机也成为了 OpenJDK中的默认虚拟机。
  • 2007年,Java平台迎来了新伙伴Clojure。
  • 2008 年,Oracle 收购了 BEA,得到了 JRockit 虚拟机。
  • 2009年,Twitter宣布把后台大部分程序从Ruby迁移到Scala,这是Java平台的又一次大规模应用。
  • 2010年,Oracle收购了Sun,获得Java商标和最具价值的Hotspot虚拟机。此时,Oracle拥有市场古 用率最高的两款虛拟机HotSpot和JRockit,并计划在未来对它们进行整合:HotRockit. Jcp组织管 理:Java语言
  • 2011年,JDK7发布。在JDK 1.7u4中,正式启用了新的垃圾回收器G1。
  • 2017年,JDK9发布。将G1设置为默认GC,替代CMS(被标记为Deprecated) 同年,IBM的J9开源,形成了现在的Open J9社区
  • 2018年,Android的Java侵权案判决,Google赔偿Oracle计88亿美元 •同年,JDK11发布,LTS版本的JDK,发布草命性的ZGC,调整了JDK授权许可
  • 2019年,JDK12发布,加入RedHat领导开发的Shenandoah GC

1.6.1.1.3. jvm

  • Sun Classic VM ->解释型
  • Exact VM --› Solaris

  • SUN/ H (K HotSpot VM

  • BEA (K TRockit -› M*. IC

  • IBM J9

  • KVM 和 CDC/CLDC Hotspot

  • Azul VM

  • Liquid VM

  • Apache Harmony

  • Microsoft JVM

  • Taobao JVM

  • Graal VM -> 2018年,"Run Programs Faster Anywhere" 之后的未来版

    image-20221006185054989

  • Dalvik VM (手机端

  • 其他jvm:

    Java Card VM. Squawk VM, JavaInJava, Maxine VM. Jikes RVM. IKVM.NET, Jam VM, Cacao VM. Sable VM. Kaffe, Jelatine JVM. Nano VM. MRP, Moxie JVM

1.6.1.1.4. jvm生命周期

1.6.1.1.4.1. 虛拟机的启动

Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。

1.6.1.1.4.2. 虚拟机的退出有如下的几种情况:

  • 某线程调用Runtime 类或system类的exit方法,或 Runtime类的halt方法,并且Java安全管理器也 允许这次exit或halt操作。
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 由于操作系统出现错误而导致Java虚拟机进程终止

1.6.1.1.5. JVM架构图

1.6.1.1.5.1. hotspot

image-20221006185647039

image-20221006190246538

性能优化--> 性能监控(命令行、可视化工具) --> 垃圾回收算法与垃圾回收器--> 内存结构与分配-->类加载 -- > class文件结构

-- > 执行引擎

1.6.1.1.6. 字节码

https://docs.oracle.com/javase/specs/index.html

The Java Virtual Machine Specification

image-20221006191620069

Javac 能够将java源码编译为字节码的前端编译器, 前端编译器还有eclipse、tomcat (ECJ 增量编译器), idea默认使用javac编译器,或者设置为AspectJ编译器ajc)

执行引擎中的JIT后端编译器

1.6.1.1.6.1. 前端编译器过程

image-20221006192729210

image-20221006192824025

1.6.1.1.6.2. 类型对应有class对象

  1. class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  2. interface: 接口
  3. [] : 数组
  4. enum:枚举
  5. Annotation: 注解@interface
  6. primitive type: 基本数据类型
  7. void

Object.class \ Comparable.class \ String[].class\ int [][].class \ ElementType.class\ override.class

1.6.1.1.6.3. 字节码指令(byte code)

指令由一个字节长度(256)、代码某种特定操作含义的操作码(opcode),以及跟随其后零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机很多指令并不包含操作数,只有一个操作码

比如:jclasslib 查看字节码指令

image-20221006213856002

opcode byte short int long float double char reference
Tipush bipush sipush
Tconst iconst lconst fconst dconst aconst
Tload iload lload fload dload aload
Tstore istore lstore fstore dstore astore
Tinc iinc
Taload baload saload iaload laload faload daload caload aaload
Tastore bastore sastore iastore lastore fastore dastore castore aastore
Tadd iadd ladd fadd dadd
Tsub isub lsub fsub dsub
Tmul imul lmul fmul dmul
Tdiv idiv ldiv fdiv ddiv
Trem irem lrem frem drem
Tneg ineg lneg fneg dneg
Tshl ishl lshl
Tshr ishr lshr
Tushr iushr lushr
Tand iand land
Tor ior lor
Txor ixor lxor
i2T i2b i2s i2l i2f i2d
l2T l2i l2f l2d
f2T f2i f2l f2d
d2T d2i d2l d2f
Tcmp lcmp
Tcmpl fcmpl dcmpl
Tcmpg fcmpg dcmpg
if_TcmpOP if_icmpOP if_acmpOP
Treturn ireturn lreturn freturn dreturn areturn

1.6.1.1.6.4. 包装类缓存

image-20221006215852988

String str=new String("a")+new String("b"); //Stringbuilder 非线程安全
        String str2="ab";
        System.out.println(str==str2); // false  

        StringBuilder.toString(); //为 new String(),和str2常量池不一致

//String 声明数据放到字符串常量池中
//jdk6 字符串常量池存放在方法区中(即永久代中)
//jdk7及以后字符串常量池存放在堆空间
        str.intern();// jdk7以后 调用顺序会影响比较结果,需要在str2声明前调用才会公用一个引用

1.6.1.1.6.5. class 文件结构

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

  • 魔数
  • class文件版本
  • 常量池
  • 访问标识(标志)
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

image-20221006223454948

image-20221006224055103

版本号和java编译器对应关系:

image-20221006224441111

预定义的class文件属性(按位置)

属性 位置 class文件
SourceFile ClassFile 45.3
InnerClasses ClassFile 45.3
EnclosingMethod ClassFile 49.0
SourceDebugExtension ClassFile 49.0
BootstrapMethods ClassFile 51.0
ConstantValue field_info 45.3
Code method_info 45.3
Exceptions method_info 45.3
RuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotations method_info 49.0
AnnotationDefault method_info 49.0
MethodParameters method_info 52.0
Synthetic ClassFilefield_infomethod_info 45.3
Deprecated ClassFilefield_infomethod_info 45.3
Signature ClassFilefield_infomethod_info 49.0
RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations ClassFilefield_infomethod_info 49.0
LineNumberTable Code 45.3
LocalVariableTable Code 45.3
LocalVariableTypeTable Code 49.0
StackMapTable Code 50.0
RuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotations ClassFilefield_infomethod_infoCode 52.0
tag项目 类型 value项目 常量类型
B byte const_value_index CONSTANT_Integer
C char const_value_index CONSTANT_Integer
D double const_value_index CONSTANT_Double
F float const_value_index CONSTANT_Float
I int const_value_index CONSTANT_Integer
J long const_value_index CONSTANT_Long
S short const_value_index CONSTANT_Integer
Z boolean const_value_index CONSTANT_Integer
s String const_value_index CONSTANT_Utf8
e 枚举类型 enum_const_value 不适用
c Class class_info_index 不适用
@ 注释类型 annotation_value 不适用
[ 数组类型 array_value 不适用

常量类型和结构

  • 常量池:class文件中资源仓库,是文件结构中和其他项目关联最多的数据类型,占用class文件空间最大的数据项目之一
  • 常量池表项:用于存放编译时期生成各种字面量符号引用,这部分将在类加载后进入方法区的运行时常量池中存放。
符号引用和直接引用关系

Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虛拟机加载Class文件的时候进行动态链接。也就是说, 在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。 当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

虚拟机在加载Class文件时才会进行动态链接,也就是说,Class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。 当虚拟机运行时,需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中。

这里说明下符号引用和直接引用的区别与关联:

  • 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只 要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目 标并不一定己经加载到了内存中。
  • 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标 的向柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例 上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定己经存 在于内存之中了

访问标识

image-20221006233220484

类索引、父类索引、接口索引集合

image-20221006233446708

字段表集合

image-20221006233619607

方法表集合

image-20221006233836902

属性表集合

1.6.1.1.6.6. 反解析工具

  • Jclasslib (idea 插件)

  • Javap

    • javap -v -p Demo.class

1.6.1.1.6.7. 字节码指令集与解析概述

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html

字节码指令集按用途大致分为9类:

  • 加载与存储指令
  • 算术指令
  • 类型转换指令
  • 对象创建与访问指令
  • 方法调用与返回指令
    • 以下5条指令用于方法调用:
      • invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态。这也是Java语言中最常见的方法分派方式。
      • invokeinterface指令用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
      • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造器)、私有方法和父类方法。这些方法都是静态类型绑定的,不会在调用时进行动态派发。
      • invokestatic指令用王调用命名类中的类方法(static方法)。这是静态绑定
      • invokedynamic:调用动态绑定的方法,这个是JDK 1.7后新加入的指令。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前面4条调用指令的分派逻辑都固化在java 虚拟机内部,而 invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
  • 操作数栈管理指令
  • 控制转移指令
  • 异常处理指令
  • 同步控制指令

1.6.1.1.7. 类加载

加载过程:同一个类加载器只会加载1次。

image-20221010135354535

  • Loading 装载阶段(查找并加载类的二进制数据,生成C1ass的实例。)

    • 通过类的全名,获取类的二进制数据流。
    • 解析类的二进制数据流为方法区内的数据结构(Java类模型)
    • 创建java. Lang.CIass类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
  • Linking 链接阶段:验证、准备、解析

    • Verify
      • 格式检查:魔数、版本、长度
      • 语义检查:是否是final、是否有父类、抽象方法实现
      • 子节码验证:跳转指令、操作数类型是否合理
      • 符号引用验证:符号引用、直接引用是否存在
    • Preparation 为类的静态变量分配内存,并将其初始化默认值

    static final 修改时准备阶段会显示赋值(编译时已分配)

    image-20221010203338186

    • Resolution :将类、接口、字段和方法的符号引用转为直接引用
  • Initialization 初始化:为类静态变量赋予正确的初始值(显示初始化)。

    执行clinit()方法(系统生成的方法,只有静态代码块或者)

  • unloading卸载

image-20221010215255814

类加载器

启动、扩展、应用、自定义;分为两大类:引导类加载器、自定义加载器

显式加载、隐士加载;通过jvm参数打印类加载信息 -XX:+TraceClassLoading

特征:

  • 双亲委派模型。但不是所有类加载都遵守这个模型,有的时候,启动类加载器所加载的类 型,是可能要加载用户代码的,比如JDK内部的Serviceprovider/ServiceLoader机制 ,用户可以在标准API框架上,提供自己的实现,JDK也需要提供些默认的参考实现。例如 Java 中JNDI、JDBC、文件系统、Cipher等很多方面,都是利用的这种机制,这种情況 就不会用双亲委派模型去加载,而是利用所谓的上下文加载器。
  • 可见性。子类加载器可以访问父加载器加载的类型,但是反过来是不允许的。不然,因为 缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑。
  • 单一性。由于父加载器的类型对于子加载器是可见的,所以父加载器中加载过的类型,就 不会在子加载器中重复加载。但是注意,类加载器“邻居”问,同一类型仍然可以被加载 多次,因为互相并不可见。

自定义加载器

  • 隔离加载类(类的仲裁-类冲突)
  • 修改类的加载方式
    • 类的加载模型并非强制,除bootstrap外,其他加载并非一定要引入或者根据实际情况在某个时间点进行按需进行动态加载
  • 扩展加载源(比如数据库、网络、电视机机顶盒)
  • 防止源码泄漏
    • java代码容易编译和串改,可以进行编译加密,那么类加载也需要自定义,还原加载的字节码

1.6.1.1.8. 内存结构

image-20221021094734406

1.6.1.1.8.1. 栈FILO原理(存储栈桢)

每个方法执行,伴随着进栈(入栈、压栈)

执行结束后出栈工作

遵循”先进后出“、”后进先出“ 原则


不同线程的栈桢不允许相互引用。

java返回函数有两种方式:一种是正常return指令,一种抛出异常,不管哪种方式,都会导致栈桢被弹出。

局部变量表

局部变量表也被称之为局部变量数组或本地变量表 定义为一个数宇数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型(8种)、对象引用 (reference),以及returnAddress类型。 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间导致其嵌套调用次数就会减少 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虛拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

识别局部变量表个数的方法:静态方法不算this,非静态方法需要加this,double和long定义占2个个数

image-20221022095248427

操作数栈

操作数栈是jvm执行引擎的一个工作区,当一个方法刚执行的时候,新栈会随之创建,这个方法的操作数栈是空的。

栈的单位深度:32bit类型占用一个栈单位深度,64bit类型占用两个栈单位深度(dubbo long)

栈顶缓存技术:将栈顶元素全部缓存在物理cpu的寄存器中,降低对内存读、写次数,提升执行引擎的执行效率

动态链接

image-20221022111129892

方法返回地址

  • 正常执行完成
  • 出现未处理异常,非正常退出

本地方法接口和本地方法栈

线程进入本地方法时,它进入全新并且不在受虚拟机限制的世界。它和虚拟机拥有同样的权限。

1.6.1.1.8.2. 堆

一个jvm实例只存在一个堆,jvm启动时已确定空间大小,是jvm最大(忽略元空间大小时)的一块内存空间。

在方法结束后,堆中对象不会马上移除,仅仅在垃圾收集的时候才会被移除。

所有对象和数组都分配带堆里,栈上分配特殊情况不直接放到堆。

所有线程共享堆,但划分线程私有缓冲区(Thread Local Allocation Buffer, TLAB)

方法区逻辑上是堆的一部分。

image-20221022120401919

image-20221022120856025

image-20221022120954879

堆参数设置

-Xms 表示堆区起始内存,等价于-XX:InitialHeapSize -Xmx 表示堆区最大内存,等价于-XX:MaxHeapSize ,堆内存超过该值会抛出OOM

-Xms和-Xmx两个参数配置相同,目的是为了能够在java垃圾回收价机制清理完堆区后不需要重新分隔计算堆区大小,从而提供性能。

heap默认最大值计算方式:如果物理内存少于192M,那么heap最大值为物理内存一半。如果物理内存大于1G,那么heap最大值为物理内存1/4 .

heap默认最小值计算方式:最少不低于8M,如果物理内存大于等于1G,那么默认值为物理内存1/64,即1024/64=16M.在启动时被初始化。

32位虚拟机,如果物理内存等于4G,那么堆内存可以达到1G. 对于64位虚拟机,如果物理内存为128G,那么堆最多可达32G.

新生代与老年代堆结构占比:

image-20221022123719026

默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3

可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5

使用-Xmn设置新生代的内存大小。一般使用默认值

-XX:SurvivorRatio=8 调整新生代比例,默认是8 .默认是6,必须jvm参数设置8才会生效

-XX:MaxTenuringThreshold=N设置对选哪个晋升年龄阈值

-XX:+/-UseTLAB 快速分配策略TLAB,空间很小紧占eden空间1%,可通过-XX:TLABWasteTargetPercent设置百分比

一旦对象TLAB分配失败,jvm会尝试使用加锁机制确保数据操作的原子性,从而直接在eden空间中分配内存

jdk8: ​ -XX: MetaspaceSize=10m -XX: MaxMetaspaceSize=10m 最小默认21M,最大默认-1 表示 无限制

jdk6: ​ -XX: PermSize=10m -XX: MaxPermSize=10m 最小默认20M,最大默认区分32位(64m)/64位(82M)

image-20221022182556647

image-20221022135839735

空间分配担保策略: -XX:HandlePromotionFailure

-XX:HandlePromotionFailure:true/false .true表示使用分配担保策略,false表示未使用

Minor GC

Full GC

在发生Minor GC之前,虛拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间 如果大于,则此次Minor GC是安全的 如果小于,则虚拟机会查看-xX:HandlePromotionFailure设置值是否允许担保失败 如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则兴试进行一次Minor GC,但这次Minor Gc依然是有风险的:如果小于或者HandlePromotionFailure=false,则改为讲行一次Fu11 GC. 在JDK 6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,观察openJDK中的源码变化,虽然源码中还定义了HandlePromotionFailure参数,但是在代码中已经不会再使用它。JDK 6 Update 24之后的规则变为只要老年代的连续 空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Fu11GC。也就是说jdk6之后默认开启true

对象分配

image-20221022130754060

针对不同年龄段对象分配原则:

优先分配到eden

大对象直接分配到老年代,尽量避免程序中出现过多大对象

长期存活对象分配到老年代

动态对象年龄判断:

如果Survivor区中相同年龄的所有对象大小总和大于Survivor空间一半,年龄大于或等于该年龄对象可以直接进入老年代无须等到MaxTenuringThreshold中要求的年龄

空间分配担保:-XX:HandlePromotionFailure

金句:

针对幸存者s0,s1区,复制之后有交换,谁空谁是to

关于垃圾回收:频繁在新生区收集,很少在老年代收集,几乎不在永久区/元空间收集

MinorGC MajorGC FullGC

针对Hotspot vM的实现,它里面的GC按照回收区域又分为两大种类型: 一种是部分收集 (Partial Gc) 一种是整堆收集 (Fu11 GC) 包含:新生代、老年代、方法区

  • 部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为: 新生代收集 (Minor GC / Young GC):只是新生代(Eden\s0\s1)的垃圾收集 老年代收集 (Major Gc / old Gc) :只是老年代的垃圾收集。 目前,只有CMS GC会有单独收集老年代的行为。 注意,很多时候Major GC会和Fu11 GC混淆使用,需要具体分饼是老年代回收还是整堆回收。

  • 混合收集 (Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有G1 GC会有这种行为

  • 整堆收集 (Fu11 GC):收集整个java堆和方法区的垃圾收集。

年轻代MinorGC触发机制:当年轻代空间不足会触发MinorGC,年轻代满指的是Eden区满,Survivor满不会引发GC。MinorGC非常频繁,会引发STW,暂停其他用户进程,等待垃圾回收结束用户才恢复运行。

老年代(Major GC/Full GC)触发机制:出现MajorGC,经常会伴随至少一次MinorGC(但非绝对,在Parallel Scavenge收集器策略里会直接进行MajorGC策略选择) ,也就是老年代空间不足时,会先尝试触发MinorGC,如果还空间不足会触发MajorGC.

MajorGC速度一般会比MinorGC慢10倍以上,STW会更长。如GC后内存还不足会报OOM

FullGC触发机制:full gc是开发或调优中尽量避免,这样暂时时间会短一些。

1、调用System.gc()、Runtime.getRuntime().gc(),系统建议执行Full GC,但是不必然执行

2、老年代空间不足

3、方法区空间不足

4、通过Minor GC后进入老年代的平均大小大于老年代的可用内存

5、由Eden区、surviro so 想 s1区复制时,对象大小小于To Space可用内存,则把该对象转存到老年代,且老年代可用内存小于该对象大小。

OOM如果解决

1、要解决OOM异常或heap space的异常,一般的手段是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏 (Memory Leak)还是内存溢出 Memory Overflow) 2、如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们的。学握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位罝。 3、如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情況,尝试滅少程序运行期的内存消耗

1.6.1.1.8.3. 方法区

image-20221022140034690

jdk7及以前习惯上把方法区称为永久代。jdk1.8开始使用元空间取代永久代。IbM J9 中不存在永久代概念

jak8: -XX: MetaspaceSize=10m -XX: MaxMetaspaceSize=10m 最小默认21M,最大默认-1 表示 无限制

jak6: -XX: PermSize=10m -XX: MaxPermSize=10m 最小默认20M,最大默认区分32位(64m)/64位(82M)

image-20221022180528911

元空间溢出:加载三方jar包多,部署工程过多;大量生成反射类

image-20221022182854351

image-20221022182934807

image-20221022183027817

1.6.1.1.9. 对象布局

创建对象的方式:

1、new \ 静态方法

2、class.newInstantce():反射方式,只能调用空无参构造,权限必须public

3、Constructor的newInstantce():反射方式,可以调用空参\带参构造器,权限没有要求

4、使用clone()当前类实现cloneable接口,实现clone(),默认浅拷贝

5、使用反序列化:从文件\数据库\网络中获取对象二进制流,反序列化内存中的对象

6、第三方库Objenesis,利用asm字节码技术,动态生成Constructor对象

对象创建步骤:

1、判断对象对应的类是否加载、链接、初始化

2、为对象分配内存:指针碰撞、空闲列表

指针碰撞(Bump the pointer):用过内存在一边,空闲在一边,中间放着指针作为分界点指示器. 使用compact整理过程收集器时使用指针碰撞。分配内存仅仅是把指针指向空闲那边挪动一段与对象大小相等距离。Serial\ParNew采用该分配

空闲列表:cms有碎片的标记清楚算法。

3、处理并发安全问题(CAS)

CAS: 失败重试、区域加锁:保证指针更新操作的原子性;

TLAB:把内存分配动作按照线程划分在不同空间之中运行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲区

4、初始化分配到的空间:初始化为零值(不包括对象头)

5、设置对象头(元数据信息):对象hashCode、gc信息、锁信息、对象数组长度等数据存储在对象头

父类数据会先存储

6、执行init方法进行初始化

image-20221022193257635

image-20221022193401279

1.6.1.1.10. 对象访问定位

image-20221022194816662

通过栈上reference访问。访问方式有2种:使用句柄访问,使用直接指针访问

句柄访问:reference中存储稳定句柄地址,对象被移动时只会改变句柄中实例数据指针,reference本身不需要修改

直接指针访问:

1.6.1.1.11. 执行引擎

将字节码指令解释/编译为对应平台上的本地机器指令。

image-20221022195430549

image-20221022195157490

1.6.1.1.11.1. 代码编译过程

半编译半解释

image-20221022195957529

javac

image-20221022200112534

Java.exe

image-20221022200402536

JIT 和 AOT编译:

jdk9 引入aot静态编译:借助Graal编译器,程序运行之前将字节码转换为机器码

.java ->.class -> .so

缺点:破坏了java一次编译到处运行,必须为每个不同硬件\os编译对应的发行包。降低了java链接过程动态性,加载代码在编译期就必须全部已知。最初只支持linux x64 java base

C1(client)和C2(Server)编译器不同的优化策略: •在不同的编译器上有不同的优化策略,C1编译器上主要有方法内联,去處拟化、冗余消除。 •方法内联:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及 跳转𨑬程 •去虚拟化:对唯一的实现类进行内联 •冗余消除:在运行期间把一些不会执行的代码折叠掉 •c2的优化主要是在全局层面,逃逸分析是优化的基础。基于逃逸分析在C2上有如下几种优化: •标量替换:用标量值代替聚合对象的属性值 •栈上分配:对于未逃逸的对象分配对象在栈而不是堆 •同步消除:清除同步操作,通常指synchronized 总结: 一般来讲,JIT编译出来的机器码性能比解释器高。 C2编译器启动时长比C1编译器慢,系统稳定执行以后,C2编译器执行速度远远快于C1编译器.

热度衰减

当超过一定时间限度,如果方法调用次数仍然不足以让它提交给即时编译器编译,那这个方法调用计数器就会减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)

-XX:-UseCounterDecay 来关闭热度衰减

-XX:CounterHalfLifeTime 参数设置半衰周期时间,单位秒

1.6.1.1.12. 垃圾回收

•讲讲JVM的gC(携程)从什么维度讲? • GC是什么?为什么要有GC?(蚂蚁金服) •垃圾回收的优点和原理。(蚂蚁金服); •垃圾回收机制等(支付宝) • Gc回收的是哪部分的垃圾?cvivo)I •垃圾回收的优点和原理?基本原理是什么?(瓜子) • GC是什么?为什么要有GC?(美因) •简述Java垃圾回收机制 (美因) •垃圾回收的优点和原理。(美团)

内存动态分配--> 垃圾收集技术

垃圾回收经典问题:

  • 哪些内存需要回收
  • 什么时候回收?
  • 如何回收?

什么是垃圾:运行程序中没有任何指针指向的对象;不及时清理会造成内存溢出。

1.6.1.1.12.1. 垃圾回收算法

垃圾判别算法

引用技术算法(java没有使用)

引用计数器属性,用于记录对象被引用的情况。

优点:实现简单,垃圾对象别与识别;判定效率高,回收没有延迟性。

缺点:

  • 需要单独字段存储计数器,这样做法增加了存储空间开销
  • 每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销
  • 引用计数器无法处理循环引用问题。
可达性分析算法(或根搜索算法\追踪性垃圾收集)

该算法也实现简单执行高效,同时解决在引用计数算法中循环引用的问题,防止内存泄漏。

GCroot 有哪些?
  • 虚拟机栈中引用对象(各个线程方法中使用到的参数、局部变量等)
  • 本地方法栈内JNI引用的对象
  • 类静态属性引用对象
  • 方法区中常量引用对象(字符串常量池里的引用)
  • 同步锁synchronized持有的对象
  • java虚拟机内部引用(基本类型class对象,常驻异常对象,系统类加载器)
  • java虚拟机内部JMXBean\ JVMT中注册的回调、本地代码缓存等

垃圾清楚阶段算法

标记清楚算法

标记:Collector从引用根节点开始遍历,标记所有被引用对象,一般都在header中记录可达对象

清楚:Collector对堆内存从头到尾进行线性遍历,如果没有标记为可达对象,则将回收

缺点:

  • 效率低:递归与全堆对象遍历2遍
  • 进行GC时,需要停止整个应用程序,用户体验差
  • 清理出的内存不连续,产生内存碎片

image-20221023141055516

复制算法(应用在新生代)

将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,

之后清除正在使用的内存块的所有对象,交互两个内存的角色,最后完成垃圾回收

  • 优点:

没有标记和清除过程,实现简单,运行高效复制过去以后保证空间的连续性,不会出现“碎片” 问题。

  • 缺点: 此算法的缺点也是很明盛的,就是需要两倍的内存空间。 对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小
  • 特别的: 如果系统中的存活对象很多,复制算法不会很理想。因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才行

image-20221023141527561

标记-压缩算法(标记整理)

执行过程:第一阶段和标记清除一样,从根节点开始标记所有被引用对象。

第二阶段将所有存活对象压缩到内存的一端,按顺序排放。之后清理边界外所有的空间

优点:此算法消除了 “标记-清除”和“复制”两个算法的弊端。) •消除了标记/清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JvM只需要持有一个内存的起始地址即可。 •消除了复制算法当中,内存减半的高额代价。

缺点:从效率上来说,标记-压缩算法要低于复制算法。 •效率不高,不仅要标记所有存活对象,还要整理所有在活对象的引用地址。 •对于老年代每次都有大量对象存活的区域来说,极为负重。 •移动对象的同时,如果对象被其他对象引用,则还高要调整引用的地址。 移动过程中,需要全程暂停用户应用程序。即:STW

image-20221023142301827

分代收集算法

不同生命周期不一样,择优选择;

image-20221023144505302

在Hotspot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点。 •年轻代 (Young Gen) 年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁。 这种情況复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因此很适 用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个survivor的设计得到 缓解。 •老年代 (Tenured Gen) 老年代特点:区城较大,对象生命周期长、存活率高,回收不及年轻代频繁。 这种情况存在大量仔活率高的对象,复制算法明显变得不合适。一般是由标记-清除或者是标记-清除与 标记-整理的混合实现。 •Mark阶段的开销与存活对象的数量成正比。 sweep阶段的开销与所管理区域的大小成正相关。 compact阶段的开销与存活对象的数据成正比。 以Hotspot中的CMS回收器为例,CMS是基于Mark-sweep实现的,对于对象的回收效率很高。而对于 碎片问题,CMS采用基于Mark-Compact算法的serial old回收器作为补偿措施:当内存回收不佳(

增量收集算法

垃圾收集线程只收集一小片区域的内存空间,接着切换应用程序线程,一次反复,直到垃圾收集完成。

总的来说通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段方式完成标记、清理或复制工作

缺点:造成系统吞吐量下降。

分区算法

1.6.1.1.12.2. 垃圾回收器(7种经典收集器)

jvm调优标准:在最大吞吐量优先的情况下,降低停顿时间

  • 串行回收器:Serial 、Serial Old
  • 并行回收器:ParNew、Parallel Scavenge 、Parallel Old
  • 并发回收器:CMS 、G1

image-20221023164803505

查看默认GC:

  • -XX:+PrintCommandLineFlags
  • Jinfo -flag 垃圾回收器参数 进程id
Serial GC(串行回收)

Hotspot中Client模式下的默认新生代拉圾收集器

Serial 采用复制算法、串行回收和”Stop-the-world”机制的方式执行内存回收。 serial old 收集器同样也采用了串行回收和” stop the world”机制,只不过内存回收算法使用的是标记-压缩算法

serial old是运行在Client模式下默认的老年代的垃圾回收器 Serial old在Server棪式下主要有两个用途:

① 与新生代的Paral1el Scavenge配合使用

② 作为老年代CMS收集器的后备垃圾收集方案

image-20221023170412923

应用在单核cpu应用程序,现在都不是单核,不适用。

ParNewGC并行回收,新生代

是Serial收集器多线程版本,采用并行回收的方式执行,两款没有任何区别。

在多核cpu环境下提升程序吞吐量,单个cpu情况下不如Serial收集器更高效

ParNew GC 能与CMS收集器配合工作

image-20221023194518927

ParallelGC 吞吐量优先,java8默认

可控制的吞吐量(Throughput),自适应调节策略

-XX:+UseParallelGC 手动指定年轻带使用Parallel并行收集器

-XX:+UseParallelOldGC 手动指定老年代使用,分别适用于新生代/老年代,jdk8默认开始

-XX:ParallelGCThreads:设置年轻代并行收集线程数.最好与cpu数量相等。

默认当cpu小于8个时等于cpu个数。cpu大于8个时等于 3+[5*cpu_count]/8]

-XX:MaxGCPauseMillis:设置最大停顿时间,单位毫秒,谨慎设置,会影响吞吐量

-XX:GCTimeRatio垃圾收集时间占总时间比例(1/(N+1)),用于衡量吞吐量大小,默认99

-XX:+UseAdaptiveSizePolicy 设置自适应调节策略

适用于后台运算而不需要太多交互的任务。列如:执行批量处理\订单处理、工资支付、科学计算等应用程序

image-20221023195257467

CMS 低延迟

JDK9 新特性:CMS标记为Deprecate

JDK14新特性:CMS垃圾回收器被删除

第一款真正意义上的并发收集器,实现了让垃圾收集线程与用户线程同时工作。

垃圾收集算法采用标记-清楚算法,会stop-the-world

image-20221023205841226

初始标记(STW):暂停时间非常短,标记与gc roots直接关联的对象。

并发标记(最耗时):从GC roots开始遍历整个对象图的过程,不会停顿用户线程

重新标记(STW):修复并发标记环节,因用户线程执行导致的数据不一致问题。

并发清理(最耗时): 清理删除掉标记阶段判断已经死亡的对象,释放内存空间。优于不需要移动存活对象,所以能并发

优点:并发收集、低延迟

缺点:

  • 会产生内存碎片,导致可用空间不足提前触发Full GC
  • 对cpu资源非常敏感,在并发阶段虽然不会导致用户停顿,但会因为占用一部分线程而导致应用程序变慢,总吞吐量降低
  • 无法处理浮动垃圾,在并发阶段由于程序工作线程与垃圾收集器线程交叉运行,会产生新的垃圾对象,cms将无法对这些垃圾对象进行标记,从而导致新产生的垃圾对象没有被及时回收, 只能依赖下次释放。

参数:

-XX:+UseConcMarkSweepGC 即:ParNew(Young区)+CMS(old区用)+Serial Old 组合

-XX:CMSInitiatingOccupanyFraction:设置堆内存使用率阈值 jdk5默认68% jdk6及以上92%

-XX:+UseCMSCompactAtFullCollection:用于执行Full GC后对内存进行压缩整理,避免内存碎片. 不过内存碎片无法并发执行,会带来停顿时间变的更长。

-XX:CMSFullGCsBeforeCompaction: 设置执行多少次Full GC后对内存空间进行压缩整理。

-XX:G1HeapRegionSize: 设置region大小,整体在1MB到32MB,且为2的N次幂

G1 区域化分代式

适用于服务端应用,具有大内存,多处理器的机器

在延迟可控情况下获取尽可能高的吞吐量,面向服务端应用垃圾收集器,兼顾吞吐量和停顿时间GC实现

JDK9 默认GC选项。分区region,

old区回收会在后台维护一个优先列表,每次允许收集时间,优先回收价值最大的Region(Garbage First)

image-20221023213102083

特性:

  • 并行并发:
    • 并行:可以多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW
    • 并发:交替执行能力,不会在回收阶段发生阻塞应用程序的情况
  • 分代收集
    • G1属于分代型垃圾回收器,会区分年轻代和永久代
    • 堆空间分为若干个区域,逻辑上年轻代和老年代
  • 空间整合
    • cms:标记-清除算法、内存碎片、若干次gc后进行一次碎片整理
    • G1将内存划分一个个region,回收region之间是复制算法,整体可看做标记-压缩算法。两种算法都避免内存碎片,这种特性有利于程序长时间运行。当java堆比较大时,G1优势明显
  • 可预测的停顿时间模型:指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

-XX: +UseG1GC 手动指定使用G1收集器执行内存回收任务。 -xx:G1HeapRegionsize 设置每个Region的大小。值是2的家,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000 -xx:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标( JVM会尽力实现,但不保证达到)。默认值是200ms -XX: ParallelGCThread 设置STW时GC线程数的值。最多设置为8 -XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数( ParallelGCThreads) 的1/4左右。 -XX: InitiatingHeap0ccupancyPercent 设置触发并发GC周期的java堆占用率阈值,超过阈值就触发GC默认45

优化建议:

  • 年轻带大小
    • 避免进行-Xmn或-XX:NewRatio等相关选项显示设置年轻代大小
    • 固定年轻代大小会覆盖暂停时间目标
  • 暂停时间目标不要过于苛刻
    • G1 gc吞吐量目标是90%的应用时间和10%的垃圾回收时间

image-20221023220020973

1.6.1.1.13. GC日志分析

-XMs60m -Xmx60m -XX: SurvivorRatio=8 -XX: +PrintGCDetails -XX:+printGCDateStamps -xLoggc: ./Logs/gc. Log

1.6.1.1.14. 参数调优

防止出现OOM,减少FullGC频率,解决运行慢、卡顿问题

1.6.1.1.14.1. 堆溢出

-XX+PrintGCDetails -XX: MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -XX: +PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:10g/gc-oomHeap.10g

java.lang.OutOfMemoryError:: Java heap space //

原因:代码中有大对象分配,可能存在内存泄漏,无法找到一块足够大的内存容纳当前对象

解决:

  • 检查是否有大对象分配,最有可能是大数组分配.

  • 通过jmap把堆内存dump,使用mat查看是否存在内存泄漏

  • 没有内存泄漏使用-Xmx加大堆内存
  • 检查是否有finalizale对象,考虑其存在的必要性

命令:

  • jps: 查看运行中进程
  • jstat: 查看jvm统计信息
  • jinfo: 实时查看和修改jvm配置参数
  • jmap: 导出内存映像文件&内存使用情况
  • jhat: jdk自带堆分析工具
  • jstack: 打印jvm中线程快照
  • jcmd: 多功能命令行
  • jstatd: 远程主机信息收集

1.6.1.1.14.2. 元空间溢出

java.lang.OutOfMemoryError:Metaspace

原因:生成大量类、方法区撑暴,无法卸载,元空间内存小

解决:检查是否有反射操作,是否元空间或永久代内存较小

ClassLoadingMXBean bean=ManagementFactory.getClassLoadingMXBean();

        while(true){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(People.class);
        enhancer.setUseCache(true);//true如果属性结构是相同时可以进行共用
        enhancer.setCallback((MethodInterceptor)(o,method,objects,methodProxy)->{
        System.out.println("我是加强类");
        return methodProxy.invokeSuper(o,objects);
        });
        People people=(People)enhancer.create();
        people.printl();

        }

1.6.1.1.14.3. GC overHead limit exceeded

超过98%时间在做GC并且回收不到2%的堆内存会抛出该异常。本质是预判性异常,实际系统没有真正内存溢出。

解决:

  • 检查是否使用死循环或者使用大内存的代码
  • 使用参数:-XX:-UseGCOverheadLimit 禁用这个检查
  • dump内存,检查是否内存泄漏,如没有则加大内存

1.6.1.1.14.4. 线程溢出

创建大量线程,超出系统限制15315

//查看命令:

cat /proc/sys/kernel/pid_max

cat /proc/sys/kernel/threads-max 系统最大线程数

ulimit -u 某用户可运行多少进程

cat /proc/sys/vm/max_map_count 一个进程可以拥有多少个VMA的数量

java.lang.OutOfMemoryError:unable to create new native Thread

数量=(跟最大的寻址空间-jvm内存-操作系统内存)/线程栈大小

64位本质线程数很大,出现这个问题是操作系统保护机制。

1.6.1.1.15. 性能调优指标

步骤:

性能监控: GC、cpu、OOM、内存泄漏、死锁、无响应

性能分析:gc日志、命令行、堆快照、查看jvm、堆栈信息

性能调优:适时增加内存\选择垃圾回收器\优化代码,控制内存\增加机器分散节点压力\设置线程数\缓存、队列

1.6.1.1.15.1. 性能测试工具Jmeter

创建测试计划--添加线程组---添加监听器

逃逸分析:方法内的变量是否仅在方法内部则没有方法逃逸

-XX:-DoEscapeAnalysis 只要开启逃逸分析,就会判断方法中的变量是否发生了逃逸,如果没发生逃逸就会栈上分配

栈上分配:如果一个对象没有发生逃逸出方法的话,就可能被优化成栈上分配。

同步消除:方法内存synchronzed(obj),编译器会对方法线程进行优化去除synchronzed锁

标量替换:-XX:-EliminateAllocations(默认开启)未发生逃逸时,对象在编译时会用标量替换

1.6.1.1.15.2. 合理设置内存:

查看老年代活动对象大小(jmap -heap ):Xms XmX是老年代存活对象的3-4倍

image-20221029184406055

如何触发full GC :

  • jmap -dump:live,format=b,file=heap.bin 将当前存活对象dump到文件,此时触发full GC
  • jmap -histo:live 打印每个class的实例数目,内存占用,类全名信息,live子参数加上后,只统计或的对象数量,此时会触发Full GC
  • 在性能测试环境,可以通过监控工具触发FullGc

参数:AdaptiveSizePolicy 对于面向外部大流量\低延迟系统,不建议启用该参数。因会动态调整eden survivor大小,有些情况survivor装不下会直接晋升老年代.导致老年代空间增加从而触发full GC;

UseAdaptiveSizePolicy和SurvivorRatio参数设置搭配使用,一起配置会导致参数失效。

1.6.1.1.15.3. CPU彪高排查方案:

  • ps aux |grep java //查看当前java进程cpu、内存、磁盘情况使用量异常进程
  • top -Hp 进程id //检查当前使用异常线程的pid
  • 把线程pid变为16进制,如31695 -> 7bcf 0x7bcf
  • Jstack 进程pid |grep -A20 0x7bcf 等到进程代码日志

1.6.1.1.16. MST

1.6.1.1.16.1. (阿里)方法的重写一定不是静态方法;

1.6.1.1.16.2. (阿里)Integer x= 128 ,int y = 128 ; x==y?答: true; 自动拆箱,值类型相等

1.6.1.1.16.3. (阿里)Integer x= 128 ,Integer y = 128 ; x==y?答: false;

image-20221010113720385

1.6.1.1.16.4. (阿里)数据类型分为几类?答:基本类型8种,引用类型3中

涉及boolean 值操作会使用int,boolean数组时会当做byte数组来访问

特殊引用类型null

image-20221010111030688

1.6.1.1.16.5. (阿里)为什么不把基础类型放到堆中?

答:堆比栈要大,但是栈比堆运算速度要快。讲复杂类型放到堆中是为了不影响栈的效率,而是通过引用去堆中查找。基本类型创建时已知道大小,引用类型创建时无法确定大小,简单数据类型比较稳定,并且占用很小内存,将它放到空间小运算快的栈中、能够提高效率。

1.6.1.1.16.6. (阿里)java中参数传递是传值还是传引用?答:值传递

1.6.1.1.16.7. (阿里)java有没有指针? 答:没有

1.6.1.1.16.8. (百度) 简述java类加载机制。答:

  • Loading 装载阶段
  • Linking 链接阶段
  • Initialization 初始化
  • unloading卸载

1.6.1.1.16.9. 一个类会加载次数?答:只能由同一个类加载器加载1次。

  • jvm类模板对象?方法区/元空间

    是Java类在JVM内存中的一个快照,JvM将从字节码文件中解析出的常量池、类宇 段、类方法等信息存储到类模板中,这样JVM在运行期便能通过类模板而获取Java类中的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用。

    反射的机制即基于这一基础。如果JVM没有将Java类的声明信息存储起来,则JVM在运行期也无法反射。

    类模型的位置: 加裁的类在jvm创建相应的类结构,类结构会在存储在方法区(JDK1.8之前:永久代;JDK1.8及之后:元空间

  • 二进制流获取方式

    • 虚拟机通过文件系统读取一个class后缀文件
    • 读入jar、zip等归档数据包,提取类文件
    • 事先存放数据库中类的二进制数据
    • 使用类似http之类的协议通过网络加载
    • 运行生成一段class二进制信息等

    1.6.1.1.16.10. 哪些类不会生成clinit 方法?答:

    • 一个类中没有声明任何类变量,也没有静态代码块时
    • 一个类中声明类变量,但是没有明确使用类变量初始化语句以及静态代码块来执行操作时
    • 一个类中包含static final 修饰的基本类型字段,这些类字段初始化语句采用编译时常量表达式 -

    1.6.1.1.16.11. clinit()调用会死锁? 答:

    Clinit()带锁线程安全,如果执行耗时很长,就会多线程阻塞引发死锁。多线程加载类时会发生。

    1.6.1.1.16.12. class.forName()和getClassLoader().loadClass()有什么区别?

    类的加载= 装载+链接(1\2\3)+初始化

    class.forName会执行到初始化环节,getClassLoader()只会执行装载

    1.6.1.1.16.13. 类加载的时机?答:主动调用

被动调用:

1、当访问静态字段时,声明这个字段类的字段会被初始化(子类引用父类静态变量除外)

2、数组定义类引用不会触发类初始化

3、引用常量不会触发此类或接口初始化,在链接阶段已显式赋值

4、getClassLoader()不会

主动使用情况如下:

1、当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化

2、当调用类的静态方法时,即当使用了字节码invokestatic指令。

3、当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者 putstatic指令。

4、当使用java.lang.reflect包中的方法反射类的方法时。Class. forName ( "com.Test ")

5、当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化

6、如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化, 该接口要在其之前被初始化。

7、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

-

-

1.6.1.1.16.14. 手写classloader

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class UserDefineClassLoder extends ClassLoader {


  private String rootPath;

  public UserDefineClassLoder(String rootPath) {
    this.rootPath = rootPath;
  }

  protected Class<?> findClass(String name) throws ClassNotFoundException {
    // 转换为以文件路径表示的文件
    String filePath = classToFilePath(name);
    // 获取指定路径class文件对应的二进制数据
    byte[] data = getBytesFromPath(filePath);
    // 自定义classloader 调用defineClass()
    return defineClass(name, data, 0, data.length);
  }

  private byte[] getBytesFromPath(String filePath) {

    FileInputStream fis = null;
    ByteArrayOutputStream baos = null;
    try {

      fis = new FileInputStream(filePath);
      baos = new ByteArrayOutputStream();

      byte[] buffer = new byte[1024];
      int len;

      while ((len = fis.read(buffer)) != -1) {
        baos.write(buffer, 0, len);
      }

      return baos.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (baos != null) {
          baos.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }

      try {
        if (fis != null) {
          fis.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return null;

  }

  private String classToFilePath(String name) {

    return rootPath + "\\" + name.replace(".", "\\") + ".class";
  }

  public static void main(String[] args) {

    try {
      UserDefineClassLoder loader1 = new UserDefineClassLoder("/data/test1/");
      loader1.findClass("com.ivan.test.User");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}

1.6.1.1.16.15. 双亲委派机制源码逻辑

image-20221020223017850

核心类库由bootstrap加载,恶意重写会被preDefineClass()保护机制

1.6.1.1.16.16. 双亲委派机制的优势和劣势

优势:避免类的重复加载,确保一个类的全局唯一性,保护核心api被串改

缺点:加载类的过程是单向,顶层classloader无法访问底层classloader加载的类

1.6.1.1.16.17. 破坏双亲委派机制举例

jdk1.2之前的都是不遵循

Jdbc.jar spi接口实现类

image-20221020224212723

热替换tomcat jsp

1.6.1.1.16.18. 什么是tomcat类加载机制?

tomcat8 中可配置<loader delegate="true" 表示遵循双亲委派机制

image-20221021091522240

1.6.1.1.16.19. jdk9 类加载器

image-20221021093925727

1.6.1.1.16.20. jvm计数器怎么计数?

存储下一条指令地址,也即将要执行的代码,执行引擎的字节码解释器工作是通过这个计数器值来选取下一条需要执行的字节码指令。

Native 方法记录wei undefined,只有java文件能生成字节码,native没有。

计数器是唯一在规范中规定不会出现oom的情况区域。每个线程独享

image-20221021100904519

1.6.1.1.16.21. 栈和堆的区别和性能谁高?

  • 栈放引用,计算快,不存在垃圾回收,但是栈会溢出,

若配置栈空间变大,会导致创建线程数减少(一个进程大概有3000-5000个线程)

查看线程栈大小(单位k):jinfo -flag ThreadStackSize pid

jdk5.0之前,默认栈大小:256k

Jdk5.0之后默认栈大小:1024k(linux\mac\windows)

  • 堆放存储,

回答角度:

角度1:GC、OOM

角度2:栈和堆执行效率

角度3:内存大小、数据结构

角度4:栈管运行,堆管存储。

1.6.1.1.16.22. 栈溢出的情况?

栈溢出:StackoverflowError; 举个简单的例子:在main方法中调用main方法,就会不断压栈执行,直到栈溢出; 栈的大小可以是固定大小的,也可以是动态变化(动态扩展)的。 如果是固定的,可以通过-xss设置栈的大小; 如果是动态变化的,当栈大小到达了整个内存空间不足了,就是拋出OutofMemory异常(java.lang.OutOfMemoryError)

1.6.1.1.16.23. 调整栈大小,就能保证不出现溢出吗?

不能。因为调整栈大小,只会减少出现溢出的可能,栈大小不是可以无限扩大的,所以不能保证不出现溢出

1.6.1.1.16.24. 分配的栈内存越大越好吗?

不是,因为增加栈大小,会造成每个线程的栈都变的很大,使得一定的栈空间下,能创建的线程数量会变小

1.6.1.1.16.25. 垃圾回收是否会涉及到虚拟机栈?

不会:垃圾回收只会涉及到方法区和堆中,方法区和堆出会在在溢出的可能。 程序计数器,只记录运行下一行的地址,不存在溢出和垃圾回收; 虚拟机栈和本地方法栈,都是只涉及压栈和出栈,可能在在栈溢出,不存在坟圾回收

1.6.1.1.16.26. 方法区定义局部变量是否是线程安全?

1.6.1.1.16.27. (渣打、顺丰、腾讯、百度)空间分配担保策略?

1.6.1.1.16.28. 内存溢出?内存泄漏?

内存溢出:应用程序占用内存增长速度非常快,造成垃圾回收已经跟不上内存消耗速度,否则不太容易出现OOM。

内存泄漏:

  • 静态集合类:长生命周期对象持有短生命周期对象的引用,尽管短生命周期对象不再使用,但因为长生命周期对象持有它的引用导致不能被回收

image-20221023152509166

  • 单例模式
  • 内部类持有外部类
  • 各种链接,如数据库链接\网络链接、io链接等
  • 变量不合理的作用域
  • 改变哈希值
  • 缓存泄漏
  • 监听器和回调

1.6.1.1.16.29. 安全点和安全区域?

安全点:并非所有地方都能停顿下来开始GC,只有特定位置能停顿开始GC,这些位置称为安全点。

  • 抢先式中断:目前没有虚拟机采用
  • 主动式中断:设置中断标志,各个线程运行到safe point时主动轮询这个标志,如果中断标识为true,则将自己进行中断挂起。

安全区域:是指一段代码片段中,对象引用关系不会发生变化,在这个区域中任何位置gc都是安全的.

1.6.1.1.16.30. Java 5种引用区别?

强引用:不回收

软引用:内存不足时回收

弱引用:发现即回收

虚引用:对象回收跟踪

终结器引用:实现对象finalize()方法。无需手动编码,其内部配合引用队列使用。

1.6.1.1.16.31. 垃圾回收器有哪些?

results matching ""

    No results matching ""