从2015年3月份跳槽到现在,已经1年多没有更新自己的博客了,也许是自己来新公司过于忙碌流程过多,也有可能是自己懈怠了,忘了程序员要不断的更新自己的知识结构,谨以此记与诸君共勉之!
题记:
程序员一直以来都被一些莫名奇妙的规范所限制,比如不能使用静态变量等等,这个问题我们不做讨论,但是作为一个优秀的程序员一定要有自己的知识架构,才能理解一些奇怪的编程规范,比如java可以不用初始化类变量,因为它(类本身)是在堆里面创建,所以初始化是多此一举,等等……
图片和部分观点来源于网络,经过自己思考形成的观点,由于作者水平有限(非科班),恳请各位斧正!
Jvm架构分析
(一)jvm架构参考http://blog.csdn.net/cutesource/article/details/5904501
(二)类执行机制参考:http://blog.csdn.net/cutesource/article/details/5904542
JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果。
(三)内存管理和垃圾回收
参考:http://blog.csdn.net/cutesource/article/details/5906705
JVM内存组成结构:由堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:
1)堆
所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下所示:
新生代:新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例
旧生代:用于存放新生代中经过多次垃圾回收仍然存活的对象
2)栈
每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果-------两个信息:a)jvm是一个小的系统或则调度器,b)允许多个线程执行
3)本地方法栈
用于支持native方法的执行,存储了每个native方法调用的状态,--------主要是jvm实现的一些本地方法4)方法区
存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(PermanetGeneration)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值
说明:1.JVM中堆内存是程序直接能使用的内存,而其他区域内存(方法区/栈区/程序计数器/本地方法栈)是JVM自己使用的,所以也将JVM中内存划为堆区和非堆区(除堆之外的其他内存)
2.非堆区内存由参数-XX:PermSize和-XX:MaxPermSize指定。前者指定最小的内存大小,后者指定最大的内存大小。
3.程序运行时,JVM会向操作系统申请一块内存,内存的一部分作为堆内存使用,一部分作为非堆内存使用
(四)JVM分别对新生代和旧生代采用不同的垃圾回收机制
新生代的GC:
新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代,用javavisualVM来查看,能明显观察到新生代满了后,会把对象转移到旧生代,然后清空继续装载,当旧生代也满了后,就会报outofmemory的异常,如下图所示
在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
(五)java内存调优,对JVM内存的系统级的调优主要的目的是减少GC的频率和FullGC的次数,过多的GC和FullGC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注FullGC,因为它会对整个堆进行整理,导致FullGC一般由于以下几种情况:
-
旧生代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 -
PemanetGeneration空间不足
增大PermGen空间,避免太多静态对象 -
统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
控制好新生代和旧生代的比例 -
System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果
总结:根据以上分析,我们的出以下结论:
一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配;
new创建的对象在堆内存中分配,出现栈的应用指向堆的内存现象