jvm原理及java性能优化

Jvm简介

jvm执行原理概述

1.Java源文件,通过编译器,产生.Class字节码文件,字节码文件通过Java虚拟机中的解释器,编译成特定机器上的机器码简单来说:通过类加载器加载字节码文件,被分配到JVM的运行时数据区的字节码会被执行引擎执行。(1)类加载器,加载.class文件(2)运行数据区:栈区、堆区、PC寄存器、本地方法栈、方法区(3)执行引擎:执行装载类方法中的指令2.类的加载是指将类的.class文件读入内存,将其放在方法区内,然后在堆区创建一个java.lang.Class对象,用来访问类在方法区内数据结构的接口。类加载器并不需要等到某个类被首次主动使用时再加载它,JVM允许类加载器在预料某个类将要被使用时就预先加载它。3.类加载过程包括:加载、验证、准备、解析、初始化(1)加载:查找并加载类的二进制数据。a. 通过一个类的全限定名来获取其定义的二进制字节流b. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构c. 在Java堆中生成一个代表这个类的java.lang.Class,作为对方法区中这些数据的访问入口(2)连接:a. 验证:确保被加载类的正确性b. 准备:为类的静态变量分配内存,并将其初始化为默认值c. 解析:把类中的符号引用转换为直接引用(3)初始化:为类的静态变量赋予正确的初始值4.类加载器启动类加载器:BootstrapClassLoader,负责加载存放在JDK\jre\lib或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库。扩展类加载器:ExtensionClassLoader,负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库。应用程序类加载器:ApplicationClassLoader,负责加载用户路径ClassPath所指定的类5.JVM类加载机制全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入父类委托:先让父类加载器驶入加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类缓存机制:保证所有加载过的类都会被缓存,当需要使用某个类时,先从缓存区寻找该Class,只有缓存区不存在该类时,才会去加载此类。双亲委派机制意义:系统类防止内存出现多份同样的字节码;保证Java程序安全稳定运行(1)当AppClassLoader去加载一个class时,它首先不会自己去加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。(2)当ExtClassLoader去加载一个class时,它首先也不会自己去加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。(3)如果BootStrapClassLoader加载失败,会使用ExtClassLoader来尝试加载。(4)如果ExtClassLoader加载失败,会使用AppClassLoader来加载(5)如果AppClassLoader也加载失败,则会爆出异常ClassNotFoundExceptionJVM存储区6.运行数据区(1)虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。栈里面存着一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息,栈的大小可以固定也可以扩展,当栈调用深度大于JVM所允许的范围,会抛出StackOverFlowError。(2)本地方法栈:主要与虚拟机用到的native方法相关,Java与非Java语言交互的一部分例如操作系统。(3)PC寄存器:也叫程序计数器。JVM支持多线程运行,每个线程都有自己的程序计数器。若当前执行的是JVM的方法,则该寄存器中保存当前执行指令的地址,若执行native方法,则为空。(4)堆:堆内存是JVM所有线程共享的部分,虚拟机启动时就已经创建。所有对象和数组都在堆上进行分配。这部分空间可通过GC进行回收,当申请不到空间时会抛出OutOfMemoryError。(5)方法区:所有线程共享。主要用于存储类的信息,常量池,方法数据,方法代码等。7.方法调用。直接引用:类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析的前提是方法在程序真正运行之前就有一个可确定的调用版本。编译期可确定调用方法的版本:静态方法、私有方法、实例构造器、父类方法。JVM中栈、堆、方法区调用过程:例子:public class AppMain {public static void main() {Test test1 = new Test();test1.a();}} 编译Java文件,生成AppMain和Test类字节码文件,启动一个jvm进程,加载AppMain.class字节码文件,将类信息存储进方法区中,然后定位到方法区中main方法,开始执行,java栈主线程找test1引用 ,这个时候Test()类还没被加载 ,所以在方法区中找不到,然后jvm会加载Test()类,将Test类信息、方法信息存储进方法区中,加载完类之后会在堆中创建Test对象,并创建一个引用指针指向方法区中的类信息。主线程中会创建一个栈帧,记录test1对Test对象的引用,创建工作就完成了。执行test1.a();语句时,会先在栈中找到test1,根据该引用定位Test对象在堆中的位置,堆中会存储该对象信息在方法区中的位置,找到a()方法的信息,并执行该方法。
jvm垃圾回收机制垃圾回收区域
JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆区和方法区则不一样这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。
垃圾回收方法
(1)标记-清除算法标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。(2)copying复制算法复制算法的提出是为了克服句柄的开销和解决内存碎片的问题。它开始时把堆分成 一个对象 面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集合(GC Roots)中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。(3)标记-整理算法标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。具体流程见下图:分代收集分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是将堆区分为新生代、老年代,在堆外还有一个持久代,在新生代中每次回收都会有大量的对象被回收,老年代中对象被回收较少,持久代中基本不被回收 新生代中按照8:1:1分为1个eden区2个survivor区,假设一个是s1,一个为s2,s1,s2时刻保持一个为空,对象在堆中初次被分配时,存放在eden区中,当发现eden区满了之后会进行一次垃圾回收,将剩余存活的对象放在s1中,此时s2是空的,如果某次eden区回收将剩余存活对象放入s1时后发现s1放不下,s1也会进行GC,将eden区和s1区剩余存活对象放入s2中,此时s1为空,下次eden区再回收时就是向s2中存放存活对象,但如果eden区和s1的存活对象过多,s2放不下,则会将这些存活对象放入老年代中。同时如果一个对象经过多次垃圾回收依然存活,它也会被放入老年代。年轻代发生的GC叫做minor GC
G1垃圾收集器
Garbage-First(G1)收集器是一种服务器式垃圾收集器,适用于具有大容量存储器的多处理器机器。他会把堆分区为一组大小相等的堆区域,每个区域都是一个连续的虚拟内存区域。某些区域集具有与旧收集器中相同的角色(eden,survivor,old)。 G1执行并发全局标记阶段以确定整个堆中对象的活跃度,G1将对象从堆的一个或多个区域复制到堆上的单个区域,并且在此过程中压缩并释放内存。G1的一个显著特点就是用户设置暂停时间G1能够做到的原因:G1是选择一些内存块,而不是整代内存回收(其他GC都是回收整个generation的内存,而回收内存所需的时间取决于内存的大小,以及实际的垃圾大小,所有回收时间是不可控的),而G1每次不回收整代内存,到底回收多少依赖用户配置的时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。
JDk各个版本区别和openjdk
JDK1.4 正则表达式,异常链,NIO,日志类,XML解析器,XLST转换器JDK1.5 自动装箱、泛型、动态注解、枚举、可变长参数、遍历循环JDK1.6 提供动态语言支持、提供编译API和卫星HTTP服务器API,改进JVM的锁,同步垃圾回收,类加载JDK1.7 提供GI收集器、加强对非Java语言的调用支持(JSR-292,升级类加载架构 JDK1.8 Lambda 表达式、方法引用、默认方法、新工具、Stream API、Date Time API 、Optional 类、Nashorn, JavaScript 引擎 元数据区取代了永久代 ,元数据空间并不在虚拟机中,而是使用本地内存。OpenJDK源代码不完整、授权协议的不同、OpenJDK不包含Deployment(部署)功能、openjdk只包含最精简的JDK
Java性能优化(代码开发规范)
尽量避免随意使用静态修饰符当某个对象被定义为static引用,那么GC通常是不会回收这个对象所占有的内存尽量使用局部变量调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快;其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。
ArrayList & LinkedList一个是线性表,一个是链表,一句话,随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指针,添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据。
尽量重用对象特别是String对象的使用中,出现字符串连接情况时应使用StringBuilder 代替,由于系统不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理。因此生成过多的对象将会给程序的性能带来很大的影响。
在java+Oracle的应用系统开发中,java中内嵌的SQL语言应尽量使用大写形式,以减少Oracle解析器的解析负担。
在java编程过程中,进行数据库连接,I/O流操作,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销。
尽量指定类、方法的final修饰符带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是 final的。为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该 类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优 化。此举能够使性能平均提高50%。
循环内不要不断创建对象引用for (int i = 1; i <= count; i++){Object obj = new Object();}这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:Object obj = null;for (int i = 0; i <= count; i++) { obj = new Object(); }这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。
将常量声明为static final,并以大写命名这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量
字符串变量和字符串常量equals的时候将字符串常量写在前面String str = “123”;if (“123”.equals(str)){…}可以避免空指针异常

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值