Java Web 深入分析(11) JVM(1)

前言

Java启动后作为一个进程运行在操作系统中,该进程要分配的内存有以下几个:

1、Java堆:

存储java内存区域,堆大小是在jvm启动时就像操作系统申请完成,其中 -Xmx和-Xms 分别表示了最大大小和初始大小。堆大小分配完成后就已经固定并属于java的gc管理。

2、线程:

jvm运行的实际程序的实体是线程,jvm在创建线程会为其分配一个堆栈大小。如果线程数大于了CPU的核数就会导致高内存和低效率。

3、类和类加载器:在堆的永久代保存了类和类加载器,同样他们本身需要占用内存。

4、NIO:

java1.4后面出现了NIO类库,一种基于通道和缓冲来执行IO的新方式,主要使用了java.io.ByteFuffer的allocateDirect()方法去分配内存。区别于传统io,该方式网络或者磁盘的数据交互都是直接在操作系统的内核空间直接发生,免去了拷贝到jvm空间和java堆上切换的耗时。

5、JNI:

java本地语言调用,实际上java运行时,诸如文件、网络、io或者其他的硬件调用都需要用到jni,所以jni也是需要占用内存。

JVM内存结构

  • PC寄存器 (线程私有) 线程执行的行号指示器
  • java虚拟机栈(线程私有)java方法执行的内存模型
  • java堆 (线程共享)垃圾堆
  • 方法区 (线程共享 ,永久代) ,类信息、常量、静态变量、编译后的代码等数据,虚拟机规范中堆的逻辑部分,别名Non-Heap(非堆)用以区分
  • 本地方法区 (native方法)为jvm执行Native方法服务
  • 运行时常量池(字面量 符号)ps:了解下String.inter()方法

java堆上面对象的分配、布局和访问

1、对象的创建(就普通java对象而言)

  • 类检查过程:常量池检查是否有该类的符号引用,该符号代表的类是否被加载、解析和初始化过。 没有就执行 类加载过程。
  • 为新生对象分配内存:假设java堆内存分为一边是空闲另一边是已用内存区域,那么将指针向空闲空间挪动一段跟新生对象大小相等距离,这种方式就被称作为“指针碰撞(bump to Pointer)” 。如果不是规整的内存区域,JVM就必须维护一个列表,该列表记录着空闲内存块,分配时就从改表找一块足够打的内存划分给对象实例并更新该表记录,这种方式就被称为“空闲列表(Free List)”。因此,Serial、ParNew等带有compact过程的收集器,系统采用是指针碰撞,而CMS这种基于Mark-Sweep算法收集器就是采用空闲列表分配。这个分配内存过程中,由于对象创建十分的频繁导致线程安全。于是有2中解决方案,其一是CAS失败重试保证原子性操作,其二是本地线程缓冲(Tread Local Allocation Buffer,TLAB),分配内存按照线程划分的不同私有空间上进行。虚拟机是否用TLAB 通过 -xx:+/-UseTLAB参数设定。
  • 已分配内存初始化为零值。保证了实例字段可以直接使用。
  • 最后虚拟机进行对象设置,该对象属于按个类,类的元数据信息,对象的哈希码,对象的GC分代年龄等。这些信息都保存在对象头中。
  • 上述步骤在虚拟机视角看是执行完new后一个新对象已经产生,但是在程序上来看还需要执行一个 init方法后才算一个能真正被程序员所用的 java对象。

2、对象的内存布局

  • 对象头:非固定数据结构尽可能小的可复用存储空间。包含hashcode、GC年代分类等。
  • 实例数据:对象有效信息包含,子类定义和继承父类的。
  • 对齐填充:HotSpot VM自动内存管理要求对象起始地址必须是8字节的整数倍。

3、对象的访问

主要分为了句柄访问和直接指针,Sun HotSpot采用直接指针访问。如下图:

OutOfMemory

堆溢出

虚拟机栈和本地方法溢出

方法区和运行时常量溢出

本机直接内存溢出

posted @ 2018-06-24 13:53  言非言  阅读(314)  评论(0编辑  收藏  举报