jvm 架构及优化
写在前面
先看一下知乎上一个有趣的问题:
Java工程师面试的时候,总是提问一些jvm如何优化的问题,这些真的在开发中有用吗,工作七年了项目中从来没有用过,并且我获得过多次优秀员工,望做过优化的大牛解答?
答一:
JVM优化肯定是有用的,可能只是题主没有遇到过这方面的需求。比如一些GC机制会引起JVM的Stop The World,也就是所有工作线程都会停下来等待GC完成。对于一些对延迟比较敏感的程序来说,这一停顿达到一百甚至是几十毫秒的时候就是难以接受的。为了解决这类问题,就需要对JVM的参数做适当的调整。比如调整堆的大小,选择合适的垃圾回收器,控制对象晋升老年代的速度等等。
作者:谢知恒
链接:https://www.zhihu.com/question/40913700/answer/88862720
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
答二:
对于楼主这种情况也是可以理解的,以前我在传统行业做行业软件的时候也是对jvm一无所知,觉得一点用没有,面试问只是装逼而已。后来踏入互联网行业才明白,这是必须掌握的,而且经常会用,遇到tp99间断性的提高,这种情况首先就会去看是否是fullGC引起的,还有就是内存溢出之类的问题,不了解jvm怎么去解决这类问题。当然传统行业软件,用户量少可能不会去监控性能,遇到内存溢出可能也就重启解决了,不了了之,下次再重启。然而,对于java开发人员来说,jvm就是一座商厦,我们在里面开店,连消防栓在哪里都不知道,哪天着火了,应急楼梯也不知道在哪里,只能坐着哭?所以还是建议楼主了解一下jvm,推荐看《深入理解java虚拟机》
作者:周易
链接:https://www.zhihu.com/question/40913700/answer/138011891
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
从上面两段回答中可以看出来,熟悉jvm架构和调优意义在于出了性能问题后,多给程序员留了一条退路。
jvm 架构
首先先附上一张jvm架构图。
上图中主要由几个部分组成:
- 类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。每当运行一个
java 程序时,就会首先动态创建相应的jvm instance(启动三个程序,就会有三个jvm实例,这点和golang runtime非常类似),然后将该程序的所有类文件通过类加载器加载到jvm中。 - 执行引擎:负责执行class文件中包含的字节码指令.执行引擎是java 虚拟机模拟运行的运算引擎,基本起到了翻译的作用。
有两种翻译的方式:- 一句话一句话翻译。也就是解释执行。将程序计算器中指向的待执行的java字节码翻译为cpu可以运行的机器指令。
- 一次性翻译。即编译执行。通过JIT(just in time)一次性将所有的字节码翻译完成。
- 内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。从上图中可以看出,主要分为五个部分。
- 本地库接口,本地方法库:操作系统所有,用于处理jvm的native code和通过jit编译后的本地代码。
jvm 执行引擎
关于jvm的执行引擎,它并不是真正软件模拟实现了cpu,最终所有计算机上的运算都是由cpu执行机器指令来处理。
有个问题?jvm执行引擎和本地代码关系如何?本地代码还需要过jvm执行引擎吗?
猜测答案:应该是需要过的,jvm封装了所有的代码,管理着程序执行的结果。
关于执行引擎采用哪种方式来运行,经验如下:
用JIT编译器来编译代码所花的时间要比用解释器去一条条解释执行花的时间要多。因此,如果代码只被执行一次的话,那么最好还是解释执行而不是编译后再执行。因此,内置了JIT编译器的JVM都会检查方法的执行频率,如果一个方法的执行频率超过一个特定的值的话,那么这个方法就会被编译成本地代码。
jvm 内存
从jvm 架构可以看出,jvm内存主要分为五部分:
- 方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆),所以大家不要搞混淆了。方法区还包含一个运行时常量池。
- java堆(Heap):存储java实例或者对象的地方。这块是GC的主要区域。从存储的内容我们可以很容易知道,方法区和堆是被所有java线程共享的。
- java栈(Stack):java栈总是和线程关联在一起,每当创建一个线程时,JVM就会为这个线程创建一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是现成私有的。
- 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
- 本地方法栈(Native Method Stack):和java栈的作用差不多,只不过是为JVM使用到的native方法服务的。
java 堆存放对象实例。
java 栈存放普通的变量及其它类型的信息。
这样的好处是对象通常占有内存很大,但个数少。栈里存放的东西个数多,占用内存少。对象是垃圾回收的主战场。
所以要分出栈和堆来。
jvm调优
传统的软件开发过程中不需要考虑到jvm调优的内容,主要出于以下的几点考虑:
- 性能不是生命线。
- 系统出问题后可以采取其它的方式进行处理,例如停机,停机后再恢复,受影响不大。
而在互联网领域,上述两个问题就变为比较严重的问题。
为了留住用户,响应时间和吞吐量就会成为必须去解决的问题。而jvm性能瓶颈主要在与jvm自带的垃圾回收机制。
内存分配机制
静态内存&动态内存
Java的内存分配原理与C/C++不同,C/C++每次申请内存时都要malloc进行系统调用,而系统调用发生在内核空间,每次都要中断进行切换,这需要一定的开销,
而Java虚拟机是先一次性分配一块较大的空间,然后每次new时都在该空间上进行分配和释放,减少了系统调用的次数,节省了一定的开销,这有点类似于内存池的概念;二是有了这块空间过后,如何进行分配和回收就跟GC机制有关了。
java一般内存申请有两种:
- 静态内存.编译时就能够确定的内存就是静态内存,即内存是固定的,系统一次性分配,比如int类型变量;java栈、程序计数器、本地方法栈都是线程私有的,线程生就生,线程灭就灭,栈中的栈帧随着方法的结束也会撤销,内存自然就跟着回收了。我们不需要管的。
- 动态内存。动态内存分配就是在程序执行时才知道要分配的存储空间大小,比如java对象的内存空间。所以这几个区域的内存分配与回收是确定的,但是java堆和方法区则不一样,我们只有在程序运行期间才知道会创建哪些对象,所以这部分内存的分配和回收都是动态的。一般我们所说的垃圾回收也是针对的这一部分。
总之Stack的内存管理是顺序分配的,而且定长,不存在内存回收问题;而Heap 则是为java对象的实例随机分配内存,不定长度,所以存在内存分配和回收的问题;
新生代&老生代
在 Java 中,堆被划分成两个不同的区域:
- 新生代 ( Young )。 生命周期较短。
- 老年代 ( Old )。生命周期较长。
- 永久代。很少被讨论。
新生代 ( Young ) 又被划分为三个区域:
- Eden
- From Survivor
- To Survivor。
GC机制简述
JVM 使用的GC算法是什么?
分代收集:即将内存分为几个区域,将不同生命周期的对象放在不同区域里。GC根据用途来说有以下三种:
-
GC(或Minor GC):收集 生命周期短的区域(Young area)。Minor GC会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移到Old generation 中。
-
Full GC (或Major GC):基于标记-清除算法,收集生命周期短的区域(Young area)和生命周期比较长的区域(Old area)对整个堆进行垃圾收集。
- Major GC :清理永久代。
GC 效率也会比较高,我们要尽量减少 Full GC 的次数。
在minor Gc 与Full Gc执行机制上,都提供了三种选择:
- 串行GC(SerialGC)
- 并行回收GC(ParallelScavenge
- 并行GC(ParNew)
可以根据具体的需要选择相应的GC。
GC的调整是在吞吐量和响应时间上做一个平衡。
jvm调优简述与工具
怎样调优?
有何工具?
Jvm调优工具有以下几种:
- Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。详细说明参考这里
- JProfiler:商业软件,需要付费。功能强大。详细说明参考这里
- VisualVM:JDK自带,功能强大,与JProfiler类似。推荐。
关于调优这块在实际业务中碰到这类的问题再进行具体分析。
有疑问加站长微信联系(非本文作者)