简单来说,JVM即Java虚拟机就是指能执行标准Java字节码的虚拟计算机。
常见的JDK厂商:
此外,还有一些开源和试验性质的JVM实现,比如Go.JVM
各种版本的JDK一般来说都符合Java虚拟机规范。
两者的区别一般来说有:
1.就开发团队具体情况来选择:比如机器的操作系统,团队成员的掌握情况,兼顾遗留项目等等。
2.当前Java最受欢迎的长期维护版本是Java8和Java11。
3.有的企业在开发环境使用OracleJDK,在生产环境使用OpenJDK。 也有的企业恰好相反。 也有的公司使用同样的打包版本。 开发和部署时只要进行过测试就没问题。
一般来说, 测试环境、预上线环境的JDK配置需要和生产环境一致。
Java 中的字节码,是指Java 源代码编译后的中间代码格式,一般称为字节码文件。
字节码文件中,一般包含以下部分:
可以说,大部分信息都是通过常量池中的符号常量来表述的。
常量是指不变的量,字母 ‘K’ 或者数字 1024 在UTF8编码中对应的二进制格式都是不变的。同样地,字符串在Java中的二进制表示也是不变的, 比如 “KK” 。
在Java中需要注意的是, final 关键字修饰的字段和变量,表示最终变量,只能赋值1次,不允许再次修改,由编译器和执行引擎共同保证。
在Java中,常量池包括两层含义:
根据 JVM规范,标准的JVM运行时数据区包括以下部分:
具体的JVM实现可根据实际情况进行优化或者合并,满足规范的要求即可。
堆内存是指由程序代码自由分配的内存,与栈内存作区分。
在Java中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可以访问堆内存。
以Hotspot为例,堆内存(HEAP)主要由GC模块进行分配和管理, 可分为以下部分:
其中,新生代和存活区一般称为年轻代。
除堆内存之外,JVM的内存池还包括非堆(NON_HEAP),对应于JVM规范中的方法区,常量池等部分:
内存溢出(OOM)是指可用内存不足。
程序运行需要使用的内存超出最大可用值,如果不进行处理就会影响到其他进程,所以现在操作系统的处理办法是:只要超出立即报错,比如抛出内存溢出错误 。 就像杯子装不下,满了要溢出来一样,比如一个杯子只有500ml的容量,却倒进去 600ml,于是水就溢出造成破坏。
内存泄漏(Memory Leak)是指本来无用的对象却继续占用内存,没有在恰当的时机释放占用的内存。
不使用的内存,却没有被释放,称为内存泄漏 。 也就是该释放的没释放,该回收的没回收。
比较典型的场景是: 每一个请求进来,或者每一次操作处理,都分配了内存,却有一部分不能回收(或未释放),那么随着处理的请求越来越多,内存泄漏也就越来越严重。在Java中一般是指无用的对象却因为错误的引用关系,不能被GC回收清理。
如果存在严重的内存泄漏问题,随着时间的推移,则必然会引起内存溢出。
内存泄漏一般是资源管理问题和程序BUG,内存溢出则是内存空间不足和内存泄漏的最终结果。
截止目前(2020年3月),JVM可配置参数已经达到1000多个,其中GC和内存配置相关的JVM参数就有600多个。 但在绝大部分业务场景下,常用的JVM配置参数也就10来个。
需要根据系统的配置来确定,要给操作系统和JVM本身留下一定的剩余空间。
推荐配置系统或容器里可用内存的 70~80% 最好。
比如说系统有 8G 物理内存,系统自己可能会用掉一点,大概还有 7.5G 可以用,那么建议配置 ‐Xmx6g 。
JVM总内存=栈+堆+非堆+堆外+Native
一般来说,JDK8及以下版本通过以下参数来开启GC日志:
‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐Xloggc:gc.log
如果是在JDK9及以上的版本,则格式略有不同:
‐Xlog:gc*=info:file=gc.log:time:filecount=0
Java8版本的Hotspot JVM,默认情况下使用的是并行垃圾收集器(Parallel GC)。其他厂商提供的JDK8基本上也默认使用并行垃圾收集器。
并行垃圾收集,是指使用多个GC worker 线程并行地执行垃圾收集,能充分利用多核 CPU的能力,缩短垃圾收集的暂停时间。 除了单线程的GC,其他的垃圾收集器,比如 PS,CMS, G1等新的垃圾收集器都使用了多个线程来并行执行GC工作。
并发垃圾收集器,是指在应用程序在正常执行时,有一部分GC任务,由GC线程在应 用线程一起并发执行。 例如 CMS/G1的各种并发阶段。
首先, G1的堆内存不再单纯划分为年轻代和老年代,而是划分为多个(通常是 2048 个)可以存放对象的小块堆区域(smaller heap regions)。 每个小块,可能一会被定义成 Eden 区,一会被指定为 Survivor 区或者 Old 区。 这样划分之后,使得 G1 不必每次都去回收整个堆空间,而是以增量的方式来进行处 理: 每次只处理一部分内存块,称为此次 GC 的回收集(collection set)。 下一次GC时在本次的基础上,再选定一定的区域来进行回收。增量式垃圾收集的好处 是大大降低了单次GC暂停的时间。
年轻代是分来垃圾收集算法中的一个概念,相对于老年代而言,年轻代一般包括:
因为GC过程中,有一部分操作需要等所有应用线程都到达安全点,暂停之后才能执 行,这时候就叫做GC停顿,或者叫做GC暂停。
缺乏经验的话,针对当前问题,往往需要使用不同的工具来收集信息,例如:
一般根据APM监控来排查应用系统本身的问题。 有时候也可以使用Chrome浏览器等工具来排查外部原因,比如网络问题。
可量化的3个性能指标:
这些指标。可以具体拓展到单机并发,总体并发,数据量,用户数,预算成本等等。
这个问题请根据实际情况回答,比如Linux命令,或者JDK提供的工具等。
可以使用 ps ‐ef 和 jps ‐v 等等。
比如: free ‐m , free ‐h , top 命令等等。
一般使用 jmap 工具来获取堆内存快照。
根据实际情况来看,获取内存快照可能会让系统暂停或阻塞一段时间,根据内存量决 定。使用jmap时,如果指定 live 参数,则会触发一次FullGC,需要注意。
JVM有一个内置的分析器叫做HPROF, 堆内存转储文件的格式,最早就是这款工具定义的。
一般使用 Eclipse MAT工具,或者 jhat 工具来处理。
上网搜索是比较笨的办法,但也是一种办法。 另外就是,各种JDK工具都支持 ‐h 选项来查看帮助信息,只要用得比较熟练,即使 忘记了也很容易根据提示进行操作。
比如GC问题、内存泄漏问题、或者其他疑难杂症等等。 然后可能还有一些后续的问题。例如:
此问题为开放性问题,请根据自身情况进行回答,可以把自己思考的答案发到本课程 的微信群里,我们会逐个进行分析点评