转载请注明原创出处,谢谢!
最近没有什么实战,准备把JVM知识梳理一遍,先以开发人员的交流来谈谈jvm这块的知识以及重要性,依稀记得2、3年前用solr的时候老是经常oom,提到oom大家应该都不陌生,那个时候也并没有从根本解决oom,由于对jvm不熟悉,只是去百度,到处都是配置jvm参数的,那个时候啥不懂,直接粘贴,但是并没有解决问题,通过这个就告诉我们作为开发人员也需要对jvm很熟悉才行,问题来了,很多人会说我的代码并没有出现oom啊,不需要关注啊,因为不理解不知道重要性,可以回头看看的我的JVM菜鸟进阶高手之路一到九篇系列,可能很多人说还没有到那么高级,不需要理解,我也告诉你也是不对的,且听我慢慢道来。
谈到jvm首先需要谈的是,JAVA虚拟机规范,这个就类似jdbc规范一样,定义了一些规范,oracle有oracle的实现,mysql有mysql的实现,JAVA虚拟机规范也一样,java虚拟机有很多,IBM、Apache Harmony等,每个都有些细节不一样,但是大体符合JAVA虚拟机规范的,由于Oracle收购SUN之后,Oralce主要有JRockit和Hotspot虚拟机了,后来将其进行整合了,不然维护两套麻烦,就和原来的struts1和struts2一样,Oralce主要是以Hotspot来的,把JRockit里面的一些优点也慢慢加入到其中。目前市面上Hotspot占用率是最高的,一般说到JAVA虚拟机基本都是Hotspot虚拟机。JAVA8虚拟机规范地址:http://docs.oracle.com/javase/specs/jvms/se8/html/index.html,按照道理应该去阅读阅读的,虽然java语言与java虚拟机有密切的关系,但是两者是完全不同的内容,像Scala、Clojure、Groovy等语言都是跑在JAVA虚拟机上面的,可以产生各种各样的跨平台语言,除了语言特性不一样,他们可以共享JAVA虚拟机带来的跨平台性、垃圾回收器、以及即使编译(看到这里都应该明白这些的JAVA虚拟机拥有的不是java语言规范定义的,java语言规范地址:http://docs.oracle.com/javase/specs/jls/se8/html/index.html),稍微解释下为什么用JAVA虚拟机就可以做到跨平台呢?依稀记得当时刚刚学习java的时候有句口号“一次编译,到处运行。”Java程序理想上,并不理会真正执行哪个平台,只要知道如何执行于JVM就可以了,至于JVM实际上如何与底层平台沟通,那是JVM自己的事。由于JVM实际上相当于Java程序的操作系统,JVM就负责了Java程序的各种资源管理。
我们要记住两点:
- JVM就是Java程序的操作系统,JVM的可执行文件就是.class文件。
- Java虚拟机屏蔽了操作系统之间的差异,但是不同的系统使用的虚拟机不同。
与其他语言相比,Java程序能够做到“编译一次,到处运行”,可见它的跨平台性非常强。其实JVM就是在操作系统层面有抽象了一层虚拟机,这样的好处可以屏蔽底层细节,有每个具体的平台的虚拟机实现即可,但是对外提供的是一致的(比如windows需要安装windows版的jdk,linux需要安装linux版的jdk就是这个原因,jvm虚拟机帮我们屏蔽到了底层的细节)。
一直有一个疑惑,Oracle的jdk和OpenJDK到底有什么关系呢?
Oracle/Sun JDK与OpenJDK的区别和联系如下: - OpenJDK原是SunMicrosystems公司为Java平台构建的Java开发环境(JDK)的开源版本,完全*,开放源码。Sun Microsystems公司在2006年的JavaOne大会上称将对Java开放源代码,于2009年4月15日正式发布OpenJDK。甲骨文在 2010 年收购SunMicrosystem之后接管了这个项目。
- oracle/Sun JDK里面包含的JVM是HotSpotVM,HotSpot VM只有非常非常少量的功能没有在OpenJDK里,那部分在Oracle内部的代码库里。这些私有部分都不涉及JVM的核心功能。所以说,Oracle/Sun JDK与OpenJDK其实使用的是同一个代码库。
- 从一个Oracle内部员工的角度来看,当他要构建OracleJDK时,他同样需要先从http://hg.openjdk.java.NET签出OpenJDK,然后从Oracle内部的代码库签出私有的部分,放在OpenJDK代码下的一个特定目录里,然后构建。值得注意的是,Oracle JDK只发布二进制安装包,而OpenJDK只发布源码。
知道关系之后,其实很多就释然了,其实阿里的jdk就是基于OpenJDK定制的,所以看看OpenJDK对理解JVM很有帮助的,OpenJDK的github地址如下:https://github.com/dmlloyd/openjdk,既然都看见了OpenJDK的源码,那么是否有兴趣编译编译。
用final可以提高性能,为什么呢?
依稀记得以前老师说,用final可以提高性能,为什么呢?由于类的加载机制,关于一个*.class如何加载进来,如何一系列的操作后续会进行介绍,由于类的加载机制会提到一些热替换,热加载,以及阅读tomcat源码的时候可以了解到他是怎么处理加载的,由于final常量在准备阶段就初始化了,而并不是在初始化结点处理的,所以可以提高程序相应效率。
申明为final的情况:
- 不需要重新赋值的变量,包括类属性、局部变量。
- 对象参数前面加final,表示不允许修改引用的指向。
- 类的方法不可以被重写。
由于final关键字,在并发锁的时候,不可变的一些情况锁是无效的
比如锁一个Integer、Double、String等是不行的,你说这些你对JVM不了解能行吗?
JAVA虚拟机对各各数据类型的表示,所以引入了关于,原码,补码,反码等概念,为什么需要补码呢?
补码的好处:
- 使用补码可以没有任何歧义的表示0。
-
补码可以很好的参与二进制的运算,补码相加符号位参与运算, 这样就简单很多了。那就可以明白为什么数据溢出的概念了,经常看到
byte a=(byte)(127+1); System.out.println(a);
如果不了解JVM怎么能懂,还有两个值相同的Integer型值进行==比较时:
Integer a=125;
Integer b=125;
Integer d=300;
Integer c=300;
System.out.println(c==d);
System.out.println(a==b);
运行结果为false、true?为什么呢? 查看源码: 这儿的IntegerCache有一个静态的Integer数组,在类加载时就将-128 到 127 的Integer对象创建了,并保存在cache数组中, 一旦程序调用valueOf 方法,如果i的值是在-128 到 127 之间就直接在cache缓存数组中去取Integer对象,超出 -128 ~ +127 范围的数字就要即时构建新的Integer对象,可以通过JVM参数 -XX:AutoBoxCacheMax来进行调整。 那么== 和equals的区别,== 是进行地址及值比较,无法对==操作符进行重载,而对于equals方法,Integer里面的equals方法 重写了Object的equals方法,所以,相同类型的包装类对象直接的值比较全部使用equals方法比较,并且能用基本数据类型 就应该用基本数据类型,这些你不了解JVM你那里知道呢?
根据IEEE754定义的浮点数的格式,所以涉及到钱的小数类型必须使用BigDecimal,禁止使用float和double,为什么呢?
不懂JVM可以?后续会讲。
慎用Object的clone方法拷贝对象,深拷贝,浅拷贝。
你不了解jvm模型咋知道呢?还有Object的finalize你不了解JVM怎么理解呢?
对于String的一些操作,String的文章最多。+运算符号
String str = "start";
for(int i=0; i<100; i++){
str = str + "hello";
}
反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
其他
HashMap、ArrayList、StringBuilder等的扩容机制,会浪费空间以及性能(可能存在跨代引用的问题),特别在并发情况下面可能会死锁,后续分享hashMap的cpu 100%情况,所以集合初始化时,尽量指定集合初始值大小,你不了解jvm怎么可以呢?还有很多框架,netty等对外内存(堆外空间),多线程相关ThreadLocal等,还有锁在java虚拟机中的实现优化,你不了解怎么可以呢?
今天仅仅是开场白,后续会有系列基础知识文章出来。大家一起进步。
个人公众号