开篇Hotspot核心图镇楼

JAVA执行流程

Java源码(xxx.java) ->
Java编译器 ->
(第一次编译生成) 字节码(xxx.class) ->
Java虚拟机 (类加载器-> 字节码校验器 -> 翻译字节码,执行引擎部分(解释器,JIT编译器和gc)  (第二次编译生成机器指令) (JIT编译还会缓存到方法区中))->
操作系统

JVM架构模型

寄存器架构--如x86下安卓虚拟机性能更好花费更少指令依赖硬件,可移植性差通常一,二,三地址指令分配,需要内存地址,所以需要空间大,16位字节对齐
JVM编译器输入指令使用栈式架构(因为JAVA为了跨平台性)设计和实现更简单,适用于资源受限系统不需要寄存器分配,零地址指令分配,通过栈执行,需要空间小,8字节对齐,但是指令数量更多不依赖硬件,可移植性好

JVM生命周期

启动虚拟机启动通过引导类bootstap class loader创建初始类来实现,内部main方法后续一起加载起来执行Java虚拟机进程执行Java程序退出程序正常执行结束程序执行中异常终止操作系统出现错误导致java虚拟机进程终止runtime类halt方法等等

Hotspot热点代码探测技术

通过计数器找到具有编译价值代码,来触发即时编译或栈上替换
通过编译器和解释器协同工作,在最优化的程序相应时间和最佳执行性能中取得平衡

类加载

类加载具体过程

class file存在于本地硬盘上(可以通过class file实例化出n个一模一样实例),通过类加载器加载到jvm中,称为DNA元数据模板存在方法区1 加载 loadingclass字节码文件加载不成功会抛出异常通过一个类全限定名获取定义此类二进制字节流将该字节流所代表的静态存储结构转化为方法区运行时数据结构在内存中生成一个代表这个类java.lang.class的对象,作为方法区这个类各种数据访问入口2 链接 linking验证(verify)目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,源数据验证,字节码验证,符号引用验证。比如java虚拟机字节码开头都有cafebabe魔术标识 准备(prepare)为类变量分配内存并且设置该类变量的默认初始值,即零值;这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。解析(resolve)将常量池内的符号引用转换为直接引用的过程。加载虚方法表符号引用就是一个类中(当然不仅是类,还包括类的其他部分,比如方法,字段等),引入了其他的类,可是JVM并不知道引入的其他类在哪里,所以就用唯一符号来代替,等到类加载器去解析的时候,就把符号引用找到那个引用类的地址,这个地址也就是直接引用事实上,解析操作往往会伴随着jvm在执行完初始化之后再执行符号引用就是一组符号来描述所引用的目标。引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_infoCONSTANT_Fieldref_infoCONSTANT_Methodref_info等。3 初始化 initialization执行类构造器方法此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。如果没有静态变量,那么字节码文件中就不会有执行类构造器方法(clinit方法),但是init类的构造器任何情况下都会有若该类有父类,JVM会保证父类clinit已经执行完毕虚拟机会保证一个类clinit多线程下会同步加锁,这也解释了为什么单例模式内部静态类的线程安全static{num = 20;//这里不会报错,虽然int num在后面设定,但是在类加载器prepare阶段类变量已经分配内存且设定默认值为0System.out.println(num);//这里会报错,非法前向引用}private static int num = 10;

常量池的理解

类加载器

分为两种引导类类加载器自定义类加载器(继承classloader的都为自定义类加载器,双亲委派下扩展类加载器,系统类加载器都为自定义类加载器,因为他们也间接继承了classloader) 系统核心库如String类都是引导类加载器,用户自定义类默认系统类加载器加载如String类这种通过引导类加载器的,通过反射获得classloader会返回null
自己实现的class普通类等,获得的classloader是系统类加载器启动类加载器(也是引导类加载器,C/C++编写保存在jvm内部,用来加载java核心库)
ExtClassLoader: Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。派生于ClassLoader类,其父类加载器为启动类加载器,它从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
AppClassLoader:java语言编写,由sun. misc. LaunchersAppClassLoader实现,其派生于ClassLoader类它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库,该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载,通过ClassLoader.getSystemClassLoader ()方法可以获取到该类加载器。
自定义ClassLoader:Java编写,定制化加载

为什么需要自定义类加载器

隔离加载类
修改类加载方式
扩展加载源
防止源码泄露

用户自定义类加载器

继承java.lang.classloader
jdk1.2之前重写loadClass但是写的比较复杂,1.2后重写findClass方法,内部通过获取字节码流转数组和defineClass方法(把字节数组转换为Java类的方法)配合使用
如果没有太复杂需求,可以直接继承URLClassLoader类,可以避免编写findClass方法和获取字节码流方式

双亲委派机制

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将她的class文件加载到内存生成的class对象。而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。我们自己实现一个String的class类,外部调用String时因为双亲委派机制不会调用我们实现的String类
并且我们自己实现的String类内部写main方法时会报错,因为String类会双亲委派机制找引用类加载器,而引用类加载器没有main方法,main方法在系统类加载器中第三方jar使用,通常接口在引用类加载器中,第三方接口的具体实现在系统类加载器中双亲委派机制优势避免类重复加载保护程序安全,防止核心API被随意篡改(比如java.lang下自己乱实现一个类,启动类加载器会识别出并报错)沙箱安全机制自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载过程中会先加载jdk自带的文件(rt.jar包中的java\lang\String.class),报错信息说没有main方法就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制.

类的主动使用和被动使用

判断两个类是否为同一个类通过两个方式类的完整类名,包括包名,必须完全一致类加载器实例对象必须一致JVM必须知道一个类型是由启动类加载器加载的还是由用户类加载器加载的。如果一个类型由用户类加载器加载的,那么jvm会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证两个类型的加载器是相同的。java程序对类的使用方式分为:主动使用和被动使用主动使用,分为七种情况
创建类的实例
访问某各类或接口的静态变量,或者对静态变量赋值
调用类的静态方法
反射 比如Class.forName(com.dsh.jvm.xxx)
初始化一个类的子类
java虚拟机启动时被标明为启动类的类
JDK 7 开始提供的动态语言支持:
java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic    句柄对应的类没有初始化,则初始化
除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

运行方法区-线程私有模块

运行方法区结构

hotspot运行方法区线程私有:程序计数器虚拟机栈栈中包含多个栈帧,每个帧包含局部变量表操作栈动态连接方法返回地址本地方法栈线程公有:堆外内存:方法区,后被放入本地内存的元空间替代常量池方法元信息类元信息堆年轻代EdenS0S1老年代JIT编译产物,代码缓存

JVM线程

Hotspot JVM内每个线程和操作系统线程直接映射
操作系统负责所有线程安排调度到任何一个可用CPU上,一旦操作系统本地线程初始化成功,它则会调用java线程中run方法
当普通线程即非守护线程全都执行完毕回收,虚拟机则可以回收JVM系统线程虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。编译线程:这种线程在运行时会将字节码编译成到本地代码。信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理

程序计数器

JVM中PC寄存器是对物理PC寄存器的抽象模拟
存储下一条指令的地址,由执行引擎读取下一条指令
如果当前执行naive本地方法,则当前程序计数器为undefined
唯一没规范OOM的区域指令偏移地址和操作指令 -> PC寄存器 -> 执行引擎 -> 操作局部变量表,操作数栈 -> 机器指令 -> CPU

虚拟机栈

跨平台,指令集小,编译器容易实现
但是性能会下降,同样功能需要更多指令栈管运行,堆管存储一个栈帧对应一个方法调用,栈顶正在调用的当前栈帧就是当前方法,定义该方法类就是当前类方法结束方式有2中:正常return结束发生异常,递归的捕获异常,没捕获到则报错作用每个栈帧保存每个方法的局部变量(8种基本数据类型,对象引用地址),部分结果,并参与方法的调用和返回局部变量 -- 成员变量基本数据变量 -- 引用类型变量优点只有进栈,出栈,效率仅次于程序计数器。且不存在垃圾回收问题Java栈大小可以自己设定动态或固定动态会OOM,固定会StackOverFlow虚拟机栈大小,linux默认1mb,windows取决于实际内存大小一个线程中栈帧是不可能引用另一个线程中栈帧,因为是线程私有的

虚拟机栈内部结构

不存在垃圾回收
一个栈帧包含局部变量表一个二维数字数组,保存方法参数和定义在方法体内的局部变量局部变量所需要大小在编译时期决定,运行时不会改变,线程私有不会有安全问题局部变量表基本储存单元是slot变量槽,32位为一个索引单位,32位以内占一个slot(包括returnAddress类型),64位(long/double)占两个slot64bit局部变量使用前一个索引即可局部变量表中每一个slot分配一个访问索引,通过访问索引找到指定的局部变量值如果当前栈帧是构造方法或实例方法创建的,则该对象引用this会存放在index为0的slot中如果某个局部变量出了作用域,则后续新变量可以回收复用前面销毁的局部变量slot位置成员变量:使用前都有默认初始化赋值类变量:链接的准备阶段,类变量默认赋值,再initialization具体赋值实例化变量:随着对象的创建会在堆空间中分配实例变量空间,并进行默认赋值局部变量:使用前必须显示赋值,否则编译不通过局部变量表是性能调优的重点,其中变量是重要的垃圾回收根节点,只要被局部变量表直接或间接引用都不会被垃圾回收操作数栈(表达式栈)通过数组实现,比如执行赋值,交换,求和等操作主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。比如存一个int值为8,8先进操作数栈,然后从操作数栈转移到局部变量表相当于JVM执行引擎的工作区,栈帧刚创建时,操作数栈是空的在编译时期确定了数组栈的深度32bit占一个栈深度,64bit则两个栈顶缓存技术栈是零地址指令,指令小,数量更多所以将栈顶元素全部缓存在物理CPU寄存器中,降低对内存读写次数,提高执行引擎效率动态连接(指向运行时常量池方法引用)每个栈帧内部包含一个指向运行常量池中该栈帧所属方法的引用这个引用目的就是为了当前方法能够实现动态连接在java源文件被编译到字节码文件中时,所有变量和方法引用都作为符号引用保存在class文件常量池中。比如一个方法调用了另外的其他的方法时,就是通过常量池中指向方法的符号引用来表示的。动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用常量池作用:为了提供一些符号和常量,便于指令识别。通过符号替代,减小文件开销,直接调用即可,更方便当一个字节码文件被装入jvm中,被调用的目标方法在编译器可知且运行期不变,该情况下符号引用转直接引用为静态链接。编译期间不可知,运行期间符号引用转直接引用为动态链接。动态链接也是接口的实现,运行时才知道调用哪个实现,多态的实现非虚方法:在编译器就确定了调用版本,这个版本运行时不可变的方法静态方法,私有方法,final方法,实例构造器,父类方法都是非虚方法打破了多态使用前提:1类继承 2方法重写invokedynamic指令使得Java从静态类型语言具备了动态类型语言特性,使得可以支持json,js等Java7中通过ASM产生该指令,8后lamda表达式可以直接生成对于类型检查在编译器则为静态类型语言,运行时则动态虚方法表每次动态分配都需要元数据中搜索,为了解决面向对象的频繁动态分配,提高效率,JVM在类方法区建立了虚方法表,使用索引表来查找,每个类中都有一个虚方法表,避免了类似父类一层一层向上判断找直接引用,表中存放各个方法的实际入口。虚方法表在类加载链接过程中创建方法返回地址(方法正常或异常退出的定义)正常退出下,保存调用该方法指令的下一条指令地址异常退出通过异常表来确定,栈帧中一般不保存信息,不会给上层调用者产生任何返回值一些附加信息不确定有,比如对程序调式提供支持的信息

本地方法接口,本地方法库

非java代码接口
本地方法栈和虚拟机栈类似
当某个线程调用一个本地方法时,它就进入了全新且不受虚拟机限制的环境,它和虚拟机有相同的权限
并不是所有JVM都支持本地方法

运行方法区-堆

JVM启动时被创建且大小被确定,是JVM管理最大一块内存空间
堆可以在物理内存上不连续,大小可以自己修改设定
堆中也有划分线程私有的缓冲区TLAB,用来提高并发性
方法结束后,堆对象不会立刻回收,会在GC时回收,堆是GC重点对象内存细分JDK8中变化 永久代->元空间年轻代 (回收频率最高)Eden 默认占4/5几乎所有对象都在eden区new出来Eden区内部还有线程私有的TLAB缓冲区,来解决线程不安全问题,提高吞吐量,称为快速分配策略JVM将TLAB作为内存分配首选,默认情况TLAB占eden区百分之1当TLAB分配失败,JVM会用加锁机制保证数据操作原子性,直接在eden空间中分配内存S0 默认1/5S1 默认1/5复制算法的特殊情况Eden区放不下了,则young gcyoung gc能放下了,继续照常。如果仍然放不下,则直接放入老年代如果老年代仍然放不下,则full gc如果老年代仍然放不下,则oom。前提是jvm不允许自动调整空间,默认自动会调整young gc会触发stop the world,会暂停用户线程老年代目前只有CMS有单独old gc老年代回收行为,old gc不是full gc。full gc是整个堆和方法区的垃圾回收G1还包含 mixed gc,收集整个新生代和部分老年代full gc会触发stop the world暂停时间更长触发full gc情况System.gc()调用,系统会建议执行full gc但是不是必然老年代空间不足方法区空间不足young gc后进入老年代平均大小大于老年代可用内存eden区放不下,youg gc后仍放不下,然后老年代也放不下-Xms 设置堆起始内存
-Xmx 设置堆最大内存
一般Xms和Xmx设置相同,目的为了java垃圾回收机制清理完堆后不需要重新分隔计算堆区大小,提高性能
默认下,Xms为电脑可用物理内存/64,Xmx为电脑可用物理内存/4查看设置的参数 jps -> jstat -gc 进程id

逃逸技术

随着JIT编译器发展和逃逸分析技术的成熟,栈上分配和标量替换优化技术将使得所有对象分配到堆上变得不绝对
经过逃逸分析后发现,一个对象没有逃逸出方法的话,则可能优化成栈上分配,无需堆上分配内存,也无需垃圾回收,这是常见的堆外存储技术
此外TaoBaoVM,其中创新了GCIH(GC invisible heap),将生命周期较长的java对象放入heap外,并且gc不能不能管理GCIH内部java对象来降低GC频率
public void my method(){V v = new V();......v = null;//在方法内部v即变成null没有发生逃逸,那么可以把该对象分配到栈上,随着方法移除,栈空间也移除
}比如使用StringBuffer sb时,方法结束时返回sb时候 return sb会逃逸,b.toString()表示返回一个新的string,StringBuffer则不会逃逸

如何快速判断是否发生逃逸

new的实体只在方法内部有效不发生逃逸
jdk7开始hotspot默认开启逃逸技术

使用逃逸分析,编译器可以对代码优化

栈上分配JIT编译器分析没有逃逸则栈上分配
同步省略JIT分析如果一个锁对象被发现只能从一个线程被访问到,那么对于这个对象操作可以考虑不同步,也就是锁消除Object o = new Object();synchronized(o){//对o业务操作}在这里每个线程来都会各自创建一个新的o,所以会发生锁消除
分离对象或标量替换有的对象可以不需要连续的存储结构存储也可以访问到,那么这个对象的部分或者全部可以不存在内存,而是存在CPU寄存器中标量:指无法在分割成更小的数据,Java中原始数据类型就是标量聚合量:还可以分割成其他标量和聚合量的数据,如Java对象JIT逃逸分析后,发现没有逃逸,就可以把一个对象分解成两个聚合量并存放在栈中,不需要在堆中再分配内存比如
private static void alloc(){Point point = new Point(1,2);
}
class Point{int x;int y;
}这里会被优化成,直接栈中分配x,y不需要new对象放入heap中
private static void alloc(){int x = 1;int y = 1;
}

在hotspot中目前对象没有分配使用逃逸分析在分配到栈上, 所有对象分配仍然都在堆上

运行方法区-方法区


Person(在方法区) person (在栈中)= new Person() (在堆中)

Java虚拟机规范中要求方法区逻辑上存在堆中,但是hotspot中独立于堆的内存空间
方法区在JVM创建时创建,大小可固定可扩展
方法区决定了系统可以保存多少类,系统定义太多类会导致OOM大量第三方jar包,Tomcat 30-50个过多工程部署,大量动态生成反射类
关闭JVM会释放方法区内存jdk6时完全永久代
jdk7是永久代开始过度改革,将StringTable和静态变量放入堆中,永久代在虚拟机设置的内存内,
jdk8使用本地内存的元空间,完全取消永久代,运行时常量池放入堆中
永久代和元空间是hotspot对方法区规范的实现永久代默认大小20.75mb,可设置最大空间32位机器64mb,64位机器82mb
元空间默认大小21mb,可设置最大空间值为-1,表示没有限制
在元空间默认21mb下,一旦触及21mb会触发full gc回收没有用的类,然后该21mb值会根据释放多少元空间重新设定

方法区内存结构

jdk1.8后静态变量和运行时常量池放入堆中
类型信息该类全名 = 包名.类名该类直接父类的完整有效名(对于interface和java.lang.Object都没有父类)该类的修饰符(public,abstract,,final某个子集等)该类直接接口的有序列表域(变量)信息域名,域类型,修饰符(public,private,protected,static,volatile,final,transient某个子集等)
方法信息方法名称,方法返回类型,方法修饰符,方法字节码,操作数栈,局部变量表及大小,异常表运行时常量池字节码文件也有常量池,classfile内有魔术,最小最大版本,常量池表,方法信息,执行编译过程等等在classfile这里的常量池表包含了编译时生成的字面量(字符串""内的值),对类型,域和方法的符号引用,常量池像数组一样通过索引访问编译时常量池经过字节码文件类加载后称为运行时常量池,此时符号引用已经变成真实直接引用运行常量池相比于编译时常量池,具有动态性。比如String在常量池中没有,则动态放入String到常量池中创建类或接口的运行时常量池所需内存空间超过方法区大小,会OOM静态变量non-final的类变量,随着类加载而加载,可以不需要类实例就可以使用全局常量final static,每个全局常量在编译时即分配,正常static实在类加载链接和初始化时分配的即时编译器后的代码缓存

永久代为什么要被元空间替换?

JRockit没有方法区,Hotspot和JRockit进行了合并永久代设置空间大小很难确定,方法区中主要垃圾回收常量池中废弃常量和不在使用的类,
而如何判断类不在使用及其复杂,永久代很难调优
元空间不在虚拟机中,而是用本地内存,元空间仅收本地内存限制,减少full gc频率

为什么jdk1.7将StringTable字符串常量池放入堆中

full gc频率低,开发中有大量String创建销毁,放入堆中能及时内存回收

方法区垃圾回收

JAVA虚拟机规范对方法区回收要求很宽松,可以类不回收,类回收判断条件很复杂,ZGC就不支持类卸载
常量池常量没有被任何地方引用就可以回收判断类回收所有类及其子类实例已经回收,类加载器已经回收,该类对象没有被任何地方引用,无法任何地方反射访问该类方法
才允许回收

JAVA对象

创建对象步骤

new Object();
其中new是运行常量池中找new直接引用,Object调用构造器1 判断对象对应的类是否加载,链接,初始化已加载过则直接使用未加载过则双亲委派下使用类加载器+包名+类名为key进行查找对应的class文件,如果没找到抛异常,找到则生成class对象
2 为对象分配内存空间如果内存规整指针碰撞(用指针分割已用空间和未用空间,指针后未用空间进行分配内存)Serial,ParNew因为使用标记整理算法,碎片化小,内存规整如果内存不规整空闲列表分类,列表记录哪些内存可用和不可用,分配后再更新空闲列表CMS标记清除算法
3 处理并发安全问题采用CAS失败重试,区域加锁保证更新的原子性每个线程预先分配TLAB
4 初始化分配到的内存所有属性设置默认值,保证对象不设值可以直接使用
5 设置对象的对象头
6 执行init方法进行初始化类构造器真正赋值

对象内存布局

普通对象对象头(8字节)哈希值,GC分代年龄,锁状态标志类型指针(4字节)指向元空间类元信息实例数据(根据具体数据判断,一个int4字节,一个long8字节推类)对齐填充 (当整个字节不能被8整除时,则填冲到被8整除)数组对象对象头(8字节)类型指针(4字节)数组长度对齐填充 (当整个字节不能被8整除时,则填冲到被8整除)

一般Java 类型指针64位,但是JVM默认开启compressedPointer会压缩到4字节。compressedOops也会压缩普通对象字节,如String默认是8字节,但是String会被压缩到4字节

例题

User{int id;String name;
}
整个类占多少字节?对象头8字节,类型指针4字节,int4字节,string4字节,对齐4字节(因为前面只有20字节不能被8整除,自动填充4字节)。总共24字节

对象访问定位

句柄访问
直接指针(Hotspot采用)

直接指针

句柄访问

直接内存

在Java堆外的,直接向操作系统申请的内存空间
基于NIO,通过存在堆中DirectByteBuffer操作native内存访问直接内存速度大于JAVA堆,读写性能高
所以在读写频繁场合考虑使用直接内存
JAVA的NIO库允许程序使用直接内存,用于数据缓冲区

零拷贝,读写速度快的原理

执行引擎

由软件自己实现,能够执行不被硬件直接支持的指令集格式
执行引擎目的是将字节码解释/编译为对应平台上的本地机器指令
执行什么字节码依赖于PC寄存器

JAVA代码编译过程

源代码,词法分析器,token流,语法分析器,抽象语法树,语义分析器,注解抽象语法树,字节码生成器,JVM字节码

JAVA执行引擎过程

JVM字节码使用JIT编译器或字节码解释器都可以,二选一JIT编译器生成机器指令(虚拟机将源代码先完整编译成和本地机器相关的机器语言,缓存机器指令,然后再执行)字节码解释器(通过PC计数器对字节码采用逐行解释字节码文件中内容翻译为对应平台本地机器指令执行)解释器优点因为字节码逐行执行,响应速度快
JIT编译器优点编译完后执行速度快
所以两者同存,可以互补虚拟机启动时,解释器先发挥作用,随着程序运行时间推移,JIT热点探测功能将有价值的字节码编译成本地指令,发挥作用可以人为设定只用解释器模式,或者只用JIT编译器模式,或者混合模式

热点代码探测何时确定JIT

前端编译器: java源码生成class字节码文件
后端运行期编译器:JIT编译器中包含C1,C2编译器
静态提前编译器:AOT编译器,直接把java源码编译成机器代码过程执行频率高的代码叫热点代码,JIT对其直接编译成本地机器指令来提升JAVA性能
HotSpot基于技数器的热点探测方法调用计数器用于统计方法的调用次数,client默认1500次,server默认1000次阈值,超过阈值则触发JIT,也可以人为设定。然后超过一定时间(半衰周期)限度调用次数不足以触发JIT,会触发热度衰减,统计次数减少一半回边计数器统计循环体执行的循环次数,和方法调用次数一起累加,当超过阈值,触发JIT

JIT中C1和C2编译器

客户端编译器对字节码简单可靠的优化,耗时短方法内联:将引用函数代码编译到引用点处,减少栈帧生成,参数传递和跳转过程去虚拟化:对唯一实现类进程内联冗余消除:把运行期间不会运行的代码折叠掉
服务端编译器对字节码较长的优化和激进优化,但优化代码执行效率更高标量替换:标量值代替聚合对象栈上分配同步消除:锁消除
默认服务端编译器

AOT编译器

运行前源码直接编译成机器代码
JDK10后Hotspot加入Graal编译器,性能直追C2编译器,未来可能替代C1C2,AOT借助了GraalAOT优点避免了第一次运行慢的预热编译
缺点违背了一次编译到处运行降低了JAVA链接过程的动态性,加载代码再编译时期就需要全部已知还需要继续优化

StringTable

String基本特性

声明final的不能被继承
jdk8以前内部定义 final char[] value用于存字符串数据,jdk9改为byte[]来节约空间
实现了Serializable和Comparable接口,表示String支持序列化和比较大小
不可变,所谓修改都是新开辟空间的新值,旧值是不变的 字符串常量池是一个hashTable存储的,为了避免hash冲突,map长度不能太小
jdk6默认长度1009,jdk7默认60013,jdk8开始最小值是1009,一个map所以不会保存重复的常量池字符串

String内存分配

jdk6再永久代中,7,8以后在堆中

字符串拼接操作

1 String s = "a"+"b"+"c" 等同于 String s = "abc"放入常量池,因为这是编译器的优化
2 但是如果拼接变量相当于是new存入堆中 String s1 = "a" String s2 = s1 + "bc" 不等同于 String s3 = "abc"
拼接原理是StringBuilder,toString()就类似于new String()
3 final String s = "",当加了final后s就编译时期直接变成常量不是变量,底层不是StringBuilder,走1模式,所以能加final最好加

intern()使用

调用intern(),返回常量池的数据,如果常量池中没有,创建然后再返回常量池地址
String s = "a" 对s直接赋值,则直接返回常量池中数据

new String(“1”) + new String(“1”)创建了几个对象

6个对象
1 new StringBuilder()对象
2 两个new String("1")
3 常量池中1
4 然后StringBuilder来append两个new String("1"),然后toString()方法又创建对象返回字符串结果另外:toString()方法中不会在常量池中产生11

Intern jdk6和以后版本的经典问题

equals 比较的是值的hashcode
而 == 比较的值的内存地址是否一样

new String("1") + new String("1")中StringBuilder来append两个new String("1"),
然后toString()方法又创建对象返回字符串结果,toString()方法中不会在常量池中产生11jdk6以后s3.intern()以后会把堆中"11"引用复制给常量池,所以s4从常量池中拿"11"时候,s3和s4引用相同
jdk6时,s3.intern()是把堆中"11"的对象复制给常量池,但不是引用,所以s3和s4引用不相同

StringTable垃圾回收

new String("xxx").intern();
使用intern可以使得new String时候堆中的引用变成对常量池中的引用,堆中就可以更快GC回收,减少内存空间

G1的String去重操作

G1去重操作默认不开启,需要手动开启Java应用测试用
堆存活数据集合内String占百分之25,堆存活数据集合重复String占百分之13.5,String对象平均长度45
也就是String equal相同的重复情况很多,G1实现String去重能避免内存浪费

垃圾回收

可达性分析法,复制算法,标记清除算法,标记整理算法

太简单了就略了

finalization

对对象被销毁之前的自定义处理逻辑,是object内方法当垃圾回收期发性没有引用指向该对象时,在GC前会调用finalize()方法
用于对象回收时进行资源释放,比如关闭文件,套接字和数据库连接等finalize()可能会导致对象复活
该方法执行时候没有保证,完全由GC线程决定,若没有GC则该方法没机会运行
该方法写的不好会严重影响GC性能由于finalize方法,虚拟机对象由三种状态可触及的:从根节点开始可以到达这个对象可复活的:对象所有引用被释放,但是finalize中可能复活不可触及的:调用了finalize方法且没有复活,finalize只会调用一次,不可能再复活

所以判断一个对象是否可以被回收,要经历两次标记过程

 1 GC root没有引用,第一次标记2 判断是否需要执行finalize方法a 该对象没有重写过finalize方法或者已经调用过finalize方法,则判定为不可触及b 重写了finalize方法且还未执行过,该对象会被插入到F-Queue队列中,由一个虚拟机自动创建的Finalizer线程执行finalize()方法c finalize()是对象逃脱死亡最后机会,之后GC会对F-Queue队列中对象二次标记,如果再finalize方法导致引用链上有了链接,则该对象会被移除即将回收集合。但是下一次没有引用发生时则不会再次调用finalize方法直接垃圾回收

分代收集算法

不同生命周期对象,采用不同收集方法,提高回收效率
比如Hotspot中的分年轻代,老年代

增量收集算法

为了解决stop the world的延时时长问题一次性处理所有垃圾需要很长时间停顿,所以可以GC线程和应用程序线程交替执行,每次GC线程只回收一小片内存区域垃圾然后就切换应用线程,循环往复知道GC完全完成缺点增加了线程切换和上下文转换的消耗,也会使得垃圾回收总体成本上升,造成系统吞吐量下降

分区算法

将堆空间分割成连续的不同小区间,根据目标停顿时间,每次合理回收若干小区间
每个小区间独立使用和独立回收,这样可以控制一次回收多少个小区间

垃圾回收相关概念

System.gc()运行期间调用该方法通知可以full gc内存溢出堆内存不足,存在大量大对象且不能被回收
内存泄漏对象不用了但是不能垃圾回收,会造成内存泄漏1 单例模式单例程序持有对外部对象引用,外部对象不能被回收2 一些提供close资源但未关闭导致内存泄漏数据库,网络,IO连接等,必须手动close,否则不能被回收stop the worldGC时,程序停顿垃圾回收的并行和并发安全点和安全区域方法调用,循环跳转,异常跳转等GC发生时如何所有线程跑到最近安全点停顿主动式中断每个安全点设置中断标志,到达安全点时主动轮询中断标志,如果中断标志位true,则将自己中断挂起当线程睡眠或者阻塞状态中无法走到安全点如何中断挂起所以引入安全区,安全区内任意位置对象引用关系不会发生变化,这个区域任何位置可以中断挂起强,软,弱,虚引用他们都是可达的软引用内存不足时回收,比如高速缓存弱引用GC即回收弱引用和软引用一样,可以指定一个引用队列,当弱引用对象被回收,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况虚引用目的在于跟踪垃圾回收过程,比如在这个对象被垃圾回收时收到一个系统通知虚引用和引用队列一起使用,回收对象时,发现他有虚引用就会被垃圾回收后,将这个虚引用加入引用队列,通知应用程序这个对象的回收情况由于虚引用可以跟踪对象回收时间,因此可以将一些资源释放操作放虚引用中执行和记录终结器引用它用来实现finalize方法不需手动编码,配合引用队列使用GC时,终结器引用入引用队列,由finalizer线程通过终结器引用找到被引用对象并调用finalize方法,第二次GC时才回收引用对象

垃圾回收器

评估垃圾回收器指标吞吐量,垃圾收集开销,暂停时间,内存占用,快速串行回收器Serial, Serial Old并行回收器ParNew,Parallel Scavenge,Parallel Old并发回收器CMS,G1

JDK14版本垃圾回收组合关系

Parallel Scavenge

和ParNew一样,都是复制算法,并行回收和stop the world机制
Parallel Scavenge注重吞吐量
高效利用CPU,适合后台运算而不需要太多交互的任务
JDK8中默认此垃圾回收器

CMS

低延迟,第一次实现GC线程和用户线程同时工作
CMS不是等老年代全部满了开始GC,而是堆内存使用率达到阈值开始GC来确保GC时还有内存运行应用程序,
如果应用程序内存不足报错,可以用serial old代替为什么CMS用标记清除而不是标记整理呢因为GC时,应用程序也在运行,不能压缩内存修改应用程序的内存地址弊端内存碎片因为GC线程会占用CPU资源,影响应用线程执行效率CMS无法处理浮动垃圾,新加入的浮动垃圾只能下一次GC才能处理

G1

是一个并行回收器,多个GC线程同时工作,且部分工作可以和应用程序同时执行
仍然分老年代,年轻代。但是把堆分割成多个物理上不连续的区域来组成老年代,年轻代
跟踪每个区域垃圾堆积的价值,后台维护一个优先表,在允许的垃圾回收时间内,优先回收价值大的区域,按照区域进行回收,所有区域大小相同且运行间不会改变
可预测停顿时间为了解决短期存在的大对象,因为不想这种对象直接放入老年代,G1就增加了Humongous区域,主要用于存储大对象,对象超过1.5个区域时放入该区域在小内存下CMS性能仍然更好,大内存G1好G1垃圾回收过程young gc -> 当堆内存使用达到阈值(默认百分之45)  young gc + 老年代并发标记 -> 混合gc -> full gc -> 开头young gc循环反复

G1的remember set记忆集

ZGC

基本region布局,暂时不设分代,使用读屏障,染色指针和内存多重映射等技术实现的可并发的标记整理算法,以低延迟为首要目标
几乎在任何地方并发执行,除了初始标记是stw4个阶段并发标记并发预备重分配并发重分配并发重映射ZGC稍微了解,不太会,算了不研究了

深入理解JAVA虚拟机大全相关推荐

  1. mysql种编译码写在哪_深入理解Java虚拟机(程序编译与代码优化)

    对于性能和效率的追求一直是程序开发中永恒不变的宗旨,除了我们自己在编码过程中要充分考虑代码的性能和效率,虚拟机在编译阶段也会对代码进行优化.本文就从虚拟机层面来看看虚拟机对我们所编写的代码采用了哪些优 ...

  2. 《深入理解Java虚拟机》(第二版)学习3:垃圾收集器

    垃圾收集器 如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现. 我们这里讨论的收集器主要是基于JDK 1.7 Update 14之后的 Hotspot VM . Serial 收 ...

  3. JAVA好书之《深入理解Java虚拟机》

    最近打算做好现有工作的前提下,扎实一下自己专业的技术知识,并将相关的经典书也记录一下.今天看了一些JVM相关的知识,这里面的经典是<深入理解Java虚拟机>,适合有点基础又想深入理解其中原 ...

  4. 深入理解Java虚拟机-如何利用 JDK 自带的命令行工具监控上百万的高并发的虚拟机性能...

    虚拟机系列文章 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析 深入理解 Java 虚拟机-如何利用 Visual ...

  5. 深入理解 Java 虚拟机 - 你了解 GC 算法原理吗

    来自:好好学Java 虚拟机系列文章 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析 深入理解 Java 虚拟机- ...

  6. 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析

    来自:好好学java 话不多说,今天就分析一下一些常用的Java虚拟机的参数设置,以及如何更好的使用! 1 JVM参数简介 首先想说的是其实这些参数我们并不是陌生的,在平时的开发和使用中经常都会遇到, ...

  7. 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析

    来自:好好学java 这篇文章主要介绍Java内存区域,也是作为Java虚拟机的一些最基本的知识,理解了这些知识之后,才能更好的进行Jvm调优或者更加深入的学习,本来这些知识是晦涩难懂的,所以希望能够 ...

  8. 深入理解java虚拟机之类文件结构以及加载

    我们都知道,java是一种平台无关的语言.java代码通过java编译器(如javac等),将.java文件编译成字节码,也就是.class文件.字节码是运行在jvm虚拟机之上的.而不同的平台则 有不 ...

  9. 深入理解java虚拟机(7)---线程安全  锁优化

    关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...

  10. 怎么把虚拟机清空内存_深入理解java虚拟机1——内存管理机制与回收机制

    文中涉及JVM底层知识大多来自<深入理解Java虚拟机>第2版,内容枯燥乏味,如果看,认真看.跟着撸一遍也可以受益良多. 1.JVM:是运行在操作系统之上的,它与硬件没有直接的交互. 运行 ...

最新文章

  1. 【转】js frame 框架编程
  2. 获取Moment Js中两个日期之间的小时差异
  3. Strut2判断是否是AJAX调用
  4. matlab程序的幂法,数值分析课程设计+幂法与反幂法MATLAB
  5. Linux 设备树 : 节点与属性的删除
  6. 软件测试作业8:分析自动售货机软件例子生成的判定表图例
  7. 谷歌云使用账号密码_如何使用Google密码检查
  8. php fpm工作原理,什么是phpfpm的工作原理?
  9. Java 中实现定时服务 在ssh框架中跟普通工程里面创建的方式
  10. flutter怎么添加ios网络权限_视频号直播间怎么添加购物车商品;超详细流程步骤。丨国仁网络资讯...
  11. 论文阅读丨神经清洁: 神经网络中的后门攻击识别与缓解
  12. so文件(1)简单的导出使用
  13. error: (-205:Formats of input arguments do not match) All the matrices must have the same data type
  14. 日历2021年日历表|2021年日历表打印版 Excel版
  15. [转]现代汉语词性分类
  16. 【2016北京集训】网络战争
  17. debian使用FTP详细配置教程
  18. Pom.xml文件教程详解
  19. 2022年G2电站锅炉司炉操作证考试题库及答案
  20. 木板切割最优matlab,矩形木板最优切割方案的设计与实现

热门文章

  1. github action自动部署构建入门
  2. oracle数据转换,Oracle数据库转换函数
  3. gateway-统一权限-认证
  4. java session 超时_Javaweb项目session超时解决方案
  5. 整理15款实用javascript富文本编辑器 转自136go
  6. C# ping 局域网扫描
  7. C#/winform 旅游管理信息系统
  8. dnf mysql密码多少_DNF 台服数据库密码加密算法
  9. 21天学通C语言-学习笔记(5)
  10. html怎么让图片在左侧文字在右边,网页设计 怎么让图片在左 文字在右