JVM
JVM--翻译 --将字节码 翻译 成为机器指令
JVM,JRE,JDK
JVM:Java virtual Machine (Java虚拟机)
JVM是干什么的呢?一次编写,到处运行?
JVM就是用来翻译字节码的。java跨平台特性。
JRE:java runtime enviroment (java 运行环境)
JRE = JVM + java核心类库 + 支持文件
JDK:java development kit (java 开发工具包)
JDK = JRE + java工具 + java基础类库
栈
FILO-先进后出
入栈和出栈
JVM核心的部分就是入栈和出栈
JVM的内存管理
JVM运行时数据区
定义:java虚拟机在执行java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。
类型:
- 程序计数器(线程私有)
- 虚拟机栈(线程私有)
- 本地方法栈(线程私有)
- 堆(线程共享)
- 方法区(线程共享)
1:程序计数器
定义:指向当前线程正在执行的字节码指令的地址(行号)
为什么需要程序计数器?
因为线程很容易被中断。
2.虚拟机栈
先进后出(First Int Last Out)
定义:存储当前线程运行方法时所需要的数据,指令,返回地址
栈帧:一个方法的运行空间
-Xss:每个线程的栈大小
虚拟机栈的入栈和出栈
局部变量表:一组变量值存储空间,用于存放参数和方法内部定义的局部变量
操作数栈:方法执行就对应着栈帧在虚拟机栈中入栈和出栈的过程(操作数栈)
动态链接:方法中指向对象的实例(多态)
铁打的虚拟机栈,流水的java方法
javap 指令:反编译,将.class文件反编译成字节码文件。其实这里的反编译是指:将.class文件反编译成可以被程序员理解的字节码文件。反编译后的文件本质上还是字节码文件。
3.本地方法栈
本地方法栈和虚拟机栈类似,只不过本地方法栈存储的是native方法。
native方法本质上不是java方法,而是调用底层系统的API。
为什么要给native方法单独开一个本地方法栈?
答:就是相当于本地方法栈,其实是针对于底层实现的api开设空间的,也就意味栈实际大多数场景下,栈的内存开辟数是一个大概值,可以基本确定;而你的程序栈,是动态分配的。不确定大小。
如果把这两个混在一起,在某些情况下可能造成大量的内存碎片。
以上三个运行时数据区是线程私有的。
4.共有数据区--方法区和堆
方法区:
- 类信息
- 静态变量
- 常量(JDK1.7+移至堆)
- JIT(编译后的代码等数据)(JDK1.7+移至堆)
堆(Heap):
存放所有的对象实例和数组
设置参数:启动时分配的内存-Xms,最大内存 -Xmx
JVM内存模型
JMM与JVM运行时数据区的区别?
JMM是为了解决多线程对共享数据访问保持一致性。
JMM中的三个代?
新生代,老年代,永久代(元空间)
新生代:
主要是用来存放新生对象
老年代:
主要存放应用程序中生命周期长的内存对象
永久代:
内存的永久保存区域 -----方法区(元空间)
新生代 : 老年代 = 1:2
Eden : from to = 8:1:1
问题:
8M已经放满了,又来了一个1M的信息怎么办?
进行一次GC,将垃圾回收
针对新生代对象"朝夕生死"的特点,将新生代划分为3块区域u,分别为Eden、From Survior、ToSurvior,比例为8:1:1。
From和To是相对的,每次Eden和From发生Minor GC(下面GC会说)时,会将存活的对象复制到To区域,并清除内存。To区域内的对象每存活一次,它的"age"就会+1,当达到某个阈值(默认为15)时,ToSurvior区域内的对象就会被转移到老年代。
JVM内存回收
GC --垃圾回收(搞卫生)
GC Roots
定义:判断对象的存活
动态链接:指向对象的实例
当没有引用指向类时,就变为不可达状态,GC会将它回收。
GC算法:
标记—清除算法(Mark-Sweep)
JDK为我们提供的工具:
内存泄露和内存溢出
内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露:memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存溢出例子:
内存溢出例子:
以上是出栈方法pop的代码实现,这段代码简单看没有问题,且可以通过任何测试,但是这段代码实际上隐藏着一个内存泄漏的问题。
哪里出现内存泄漏了呢?如果一个栈先增长,再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为,栈内部维护着对这些对象的过期引用(obsolete reference)。 所谓的过期引用,是指永远也不会再被解除的引用。在本例中,凡是在elements数组的“活动部分(active portion)”之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素。
这个问题的修复比较简单,一旦对象引用已经过期,将该对象的引用置空,这样方便垃圾回收器及时将其回收。
代码修改为:
说到这里,不禁想起了Java中ArrayList的实现中,remove方法的实现
看jdk源码便可知道,ArrayList是通过数组来实现的,那么在实现list的remove方法时,源码中特别有一行elementData[--size] = null,其背后注释为clear to let GC do its work,将其清空,让垃圾回收器工作,这样就很好的避免了内存泄漏的情况发生了。
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出.
以发生的方式来分类,内存泄漏可以分为4类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。