初识JVM

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. 虚拟机栈(线程私有)
  3. 本地方法栈(线程私有)
  4. 堆(线程共享)
  5. 方法区(线程共享)

1:程序计数器

定义:指向当前线程正在执行的字节码指令的地址(行号)

为什么需要程序计数器?

因为线程很容易被中断。

2.虚拟机栈

先进后出(First Int Last Out)

定义:存储当前线程运行方法时所需要的数据,指令,返回地址

栈帧:一个方法的运行空间

-Xss:每个线程的栈大小

虚拟机栈的入栈和出栈

局部变量表:一组变量值存储空间,用于存放参数和方法内部定义的局部变量

操作数栈:方法执行就对应着栈帧在虚拟机栈中入栈和出栈的过程(操作数栈)

动态链接:方法中指向对象的实例(多态)

 

铁打的虚拟机栈,流水的java方法

 

javap 指令:反编译,将.class文件反编译成字节码文件。其实这里的反编译是指:将.class文件反编译成可以被程序员理解的字节码文件。反编译后的文件本质上还是字节码文件。

3.本地方法栈

本地方法栈和虚拟机栈类似,只不过本地方法栈存储的是native方法。

native方法本质上不是java方法,而是调用底层系统的API。

为什么要给native方法单独开一个本地方法栈?

答:就是相当于本地方法栈,其实是针对于底层实现的api开设空间的,也就意味栈实际大多数场景下,栈的内存开辟数是一个大概值,可以基本确定;而你的程序栈,是动态分配的。不确定大小。

如果把这两个混在一起,在某些情况下可能造成大量的内存碎片。

以上三个运行时数据区是线程私有的。

4.共有数据区--方法区和堆

方法区:

  1. 类信息
  2. 静态变量
  3. 常量(JDK1.7+移至堆)
  4. 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. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 

 

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值