学过java的就应该知道 JVM虚拟机是Java 这门编程语言的一大特点,所以,了解Java的虚拟机,能更好地解决一些内存问题,和一些代码的优化问题。
一、虚拟机内存中的堆和栈
在我们的Java虚拟机中的内存堆栈模型图,如下:
对程序中的对象或线程所存在的区域,简单描述如下:
方法区中:类class信息、static静态字段、运行时常量等
堆中: 对象(数组也是对象)
栈中:线程执行代码的地方,每个方法调用一个栈帧,通过局部变量、this关键字引用)它的特点是:先进后出(首先执行的,最后退出,例如,某程序首先从main()方法开始执行,程序将结束的时候,也是要回到main()方法中)
常见的几种内存溢出异常:
OutOfMemoryError:Metaspace:方法区内存溢出
OutOfMemoryError:Heap space 堆内存溢出错误
StackOverflowError栈内存溢出
二、垃圾回收机制
GC发现垃圾的算法
-
引用计数法,特点:在虚拟机中有一个变量专门用来计算某个对象被使用的次数,一个对象被赋值给一个变量时,引用计数+1; 持有引用的变量退出作用域时,引用计数-1,刚开始创建的时候,计数器默认为1 ,如果变成0,则成为垃圾,将会被垃圾回收器回收,它的局限性就是无法发现两个相互引用的垃圾对象。
-
根搜索算法,特点:根据某个对象是否有根来判断是否为垃圾(根:如下图中的对象实例1被其他来自方法区或者某栈帧引用了这个对象,那么,这个对象就是有根的,而对象实例3虽然被对象实例5引用,但是,对象实例5缺没有被其他方法区或者栈引用,所以对象实例3和对象实例5是没有根的,也就是垃圾对象)
Java虚拟机中常见的GC回收垃圾(清理垃圾)算法:
-
标记清除算法
特点: 清除效率高,性能高、简单,但是会造成内存碎片太多,使内存使用效率不高
-
分段复制算法
特点:将内存空间,分成两半用一半,留一半,避免内存碎片,效率高,但是执行动静太大,容易造成服务器卡顿
-
标记-整理算法
特点:整理成,连续的标记清除法的改良版
-
分代收集算法
新生代:伊甸园:刚创建的对象,通常寿命较短,(短命对象) 在这个区域中将会被回收掉大量的垃圾对象
生存区(From, To 区),经过伊甸园的筛选,存活下来的对象,就会存到这个区域中,这个区域中的对象被回收的几率较大
老年代:通过层层回收,从新生代存活下来的对象,就进入了老年代,这个区域中的对象,被回收几率减小,
永久代:经过很多次的回收还存活下来的对象,说明,这些对象可能最近这一段时间不会是垃圾,这个区域的对象,基本上是不会回收的。 -
内存泄漏:该回收的内存/对象,无法回收,导致内存一直占用
内存溢出:内存不够了,导致,程序崩溃,程序代码写的不够规范,不够优化,所以导致,产生很多的"垃圾", 无法回收这个时候,(内存泄漏),达到一定程度,,就会导致内存溢出
内存泄漏和内存溢出的关系为:内存溢出,很大的可能是由于内存泄漏而导致的。
垃圾回收清理算法
垃圾回收级别:
第一级:Minor GC (小范围回收),对小范围内的垃圾进行回收和整理, 主要是在,新生代
第二级:Major GC (大范围回收) 对大范围内的所有垃圾进行回收和整理, 新生代和老年代 ,内存占用大概超过60%甚至70% 的时候启用第二等级的回收策略(加大力度回收垃圾)
第三级:Full GC (所有范围回收) 对小范围的垃圾回收和整理,所有的区域,(新生代,老年代,永久代)
三、高效代码技巧
- 尽量不要在循环中:使用try ---catch,new 对象等操作
- 把频繁使用的短命对象(在伊甸园中的对象,称为短命对象),缓存起来
- 尽可能使用栈内变量(方法内局部变量)
- 不要用异常来控制代码流程
- 用线程池,连接池,不要自己创建
- 学会读java核心API源代码
最后,推荐两本书,希望对你学习java有所帮助:
-
java基础:《java编程思想(第四版)》
-
Java进阶:《Effecitve java》