初识jvm

之前零零散散的接触过jvm,今天在这里总结一下:

 

我们先来说说JDK和JRE:

    jdk的目录  

 

 jdk(Java Development Kit )是java语言软件开发包,面向程序开发人员,如果只要运行java文件那么有jre也足够

而jre(Java Runtime Environment)是java运行时环境,jdk中包含jre,

上图Src.zip为java中类的源码

  

Java.exe是java虚拟机    javac.exe是java前端编译器

在dos下编译java:

      javac  文件名  这样就会生成一个.class字节码文件

而运行就不用跟上后缀!!

什么是jvm?   

   jvm(java Virtual Machine),它是一个虚构出来的计算机,是一个平台,也正是jvm的存在使得程序脱离操作系统的限制,一次编译,到处执行;

  为何jvm可以一次编译到处执行呢?

    

  这是字节码的功劳,大家都知道,java程序编译会产生一个.class的字节码文件,而不是本地机器指令;其实很容易想通,就比如你只会汉语,现在要让其他国家的人来看懂你写的文章,你只要准备一个翻译软件就好,而jvm就是充当这个翻译软件的职责,因此,解释/编译为对应平台的机器指令由jvm来完成。

 

javac.exe 来编译java源码为字节码,需要4个步骤:词法解析→语法解析→语义解析→生成字节码

  当然,JVM并不会与java语言终生绑定,只要是满足java虚拟机的内部指令集的都可以运行在JVM;

 

主流的jvm:Hospot(热点)

    

  Hotspot vm具备热点探测功能,可以通过这个功能将一段频繁调用的方法标记为“热点代码”,通过内嵌的jit编译器编译成本地机器指令。编译器和解释器并存,协同工作,边编译变解释;且Hotspot内嵌两个编译器c1和c2,c1对字节码进行简单和可靠的优化,c2会启动一些编译耗时更长的优化;

 

 实际开发:

 

  Eclipse使用的是ECJ编译器;javac是全量编译,也就是一次性编译所有源码,而ECJ是增量编译,每当保存,ECJ就会将源码编译成字节码,如果增加了代码,那么之前的代码没有改动的情况下不用再次编译,Tomcat同样使用ECJ来编译jsp文件,因为之前的文章里也写过,jsp就是java类;

 

Class文件:

     在来说说class文件,java不以此文件的后缀作为标识,而是开头以(0x开头表示16进制,0开头表示八进制)0xcafebabe来做标识,这段标识称为magic

看下图:

 

 

在来看看jvm的运行时的内存结构图:

 

 

 

首先 Jvm的内存区可以分成线程共享线程私有两类;

 

 线程共享:方法区和java堆(heap)和运行时的常量池3个内存区(其实在方法区中);

 

Heap:

  Hava堆区在jvm启动的时候被创建,它在实际的内存空间可以是不连续的;用来存储对象实例,也是GC执行垃圾回收的重点区域;

  分代收集:存储在jvm中的java对象有两类:一类生命周期短的瞬时对象,另一类生命周期非常长,有可能与jvm生命周期保持一致,因此对不同生命周期的java对象,应该采取不同的回收策略,分代收集由此而生;

  正是需要分代收集,因此将java堆细分成了新生带(YoungGen)和老生带(OldGen),其中新生带又分成了Eden,From Survivor、to survivor空间;

来看看下图:

唉!? Tlab?   不要急,下文中将会提到

 

 

方法区:

  线程共享访问,方法区存储了每一个类的结构信息,方法区在jvm启动时创建,内存上可以不连续;在hospot中,方法区物理上还是属于java堆,是逻辑上的独立,方法区也可能发生内存溢出

   运行时常量池:属于方法区的一部分,每加载一个类,就会建立与之对应的运行时常量池,内存来自方法区;

Pc计数器:

  是线程私有的,生命周期和jvm一致,如果当前线程执行的是一个java方法,那么pc计数器的值就是正在执行的字节码指令的值,但如果执行的是native方法,那么将为空;是内存区中唯一一个没有明确规定抛出oom的;

Java栈:

线程私有,生命周期与线程保持一致,java栈用与存储栈帧,而栈帧用于存放局部变量表,操作数栈,以及方法出口;

本地方法栈:

  用于支持本地方法(native,如用c/c++编写的方法)执行,

在来说说内存的OOM:

    内存泄露:申请了没释放,对此空间失去了控制,堆积将造成内存溢

 

 内存溢出:内存不够用了

 

jvm的内存分配:

  jvm实现自动分配,不用像c一样需要手动;

 

  快速分配策略: 当new一个对象时,由于对象实例的创建在jvm中是非常频繁,在多线程并发的环境下,从堆(线程共享)中分配内存是不安全的,jvm会优先选择TLABThread Local Allocation,本地线程分配缓冲区)中为对象实例分配内存空间,TLAB是堆区的一块线程私有区域,在新生代中的Eden中,这种方式成为快速分配策略;

 

   当为对象成功分配好所需的内存空间后,jvm接下来要做的事情就是初始化对象实例,首先对分配好的内存空间进行零值初始化; 

  amazing!!  输出为0;

 

 

GC(Garbage Collector)

  垃圾收集器的主要任务就是:内存的动态分配和垃圾回收;

  回收垃圾的第一步,当然是要先辨别出哪些对象是需要回收的,也就是说哪些对象是死亡的就给它们做上标记;那么一个对象怎么才能算是死亡了呢?  简要的说就是当对象不被一个活得对象所引用,那么这个对象就是死亡了。

那么什么时候回收呢?  

在内存的使用达到一定的阈值的时候,进行垃圾回收。

常用的垃圾标记算法:引用计数算法和根搜索算法  

引用计数算法:在每个对象中,使用一个独立的引用计数器,当计数器为0的时候,就为死亡,但是这样存在一个问题,就像死锁一样,相互占有且等待,而对象相互引用且死亡,那么计数器都不为0,这时,就不会去回收这样的对象

根搜索算法:这是更加常用的方法,以根对象集合作为起始点,按照从上至下的方式搜索(树形结构),不被更对象所连接的对象就是死亡的对象

  

 

   

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值