深入理解Java虚拟机(一)

深入理解Java虚拟机

Java虚拟机内存区域

对于从事C/C++程序开发的开发人员来说,在内存管理区域,他们既拥有每一个对象的”所有权“,又担负着对每一个对象声明从偶开始到终结的责任。

 

对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,虽然使用虚拟机管理内存十分方便,但是一旦uxianle内存泄露和溢出方面的问题,如果不了解虚拟机怎样使用内存,那么排查错误、修正问题将会成为一项十分艰难的工作。

Java虚拟机运行时数据区

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域各有各的用途,以及创建和销毁时间,有的区域随着虚拟机进程的启动而一直存在,有些区域是依赖用户线程的启动和结束而建立和销毁。

数据区分类

 + 线程隔离区域+ 线程隔离区域
 + 所有线程共享的区域+ 所有线程共享的区域

运行时常量池

直接内存

直接操作内存

直接内存和堆内存比较

 

HotSpot虚拟机对象探秘

对象的内存布局

对象头

对象的创建的过程

类加载检查

为新生对象分配内存

初始化

对象的访问定位

 

句柄访问方式

直接指针访问方式

垃圾收集策略与算法

判定对象是否存活

引用计数法

 

可达性分析法

引用的种类

强引用(Strong Reference)

软引用(Soft Reference)

弱引用(Weak Reference)

虚引用(Phantom Reference)

回收堆中无效对象

判定 finalize() 是否有必要执行

对象重生或死亡

 

回收方法区内存

判定废弃常量

判定无用的类

 

垃圾收集算法

标记-清除算法

复制算法(新生代)

分配担保

标记-整理算法(老年代)

分代收集算法

HotSpot 垃圾收集器

新生代垃圾收集器

Serial 垃圾收集器(单线程)

ParNew 垃圾收集器(多线程)

Parallel Scavenge 垃圾收集器(多线程)

老年代垃圾收集器

Serial Old 垃圾收集器(单线程)

Parallel Old 垃圾收集器(多线程)

CMS 垃圾收集器

G1 通用垃圾收集器

内存分配与回收策略

对象优先在 Eden 分配

 

大对象直接进入老年代

长期存活的对象将进入老年代

动态对象年龄判定

空间分配担保


JVM 性能调优

使用 64 位 JDK 管理大内存

  • 使用 32 位 JVM 建立逻辑集群

    • 调优案例分析与实战

      场景描述

      分析

      直接内存的回收过程

如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常

方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器后的代码缓存等数据。

Java方法区(堆)

 

如果Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

Java堆既可以被实现为固定大小的,也可以实现为可扩展的,所以Java虚拟机都是按照扩展来实现的(通过参数-Xmx和-Xms设定)。

Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作”GC堆“。从内存角度来看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以java堆中经常会出现”新生代“、”老年代“等名词。

堆内存是虚拟机所管理的内存最大的一块,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例(new Object()),在Java世界里几乎所有的对象实例都在这里分配内存。

Java堆内存

 

Java 堆所使用的内存不需要保证是连续的。而由于堆是被所有线程共享的,所以对它的访问需要注意同步问题,方法和对应的属性都需要保证一致性。

不同的区域存放不同生命周期的对象,这样可以根据不同的区域使用不同的垃圾回收算法,更具有针对性。

  • 线程共享,整个 Java 虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java 虚拟机栈、本地方法栈都是一个线程对应一个。

  • 在虚拟机启动时创建。

  • 是垃圾回收的主要场所。

  • 进一步可分为:新生代(Eden区 From Survior To Survivor)、老年代。

堆的特点:

线程共享区域共享的是使用堆来存放对象的内存空间,几乎所有对象都在堆内存中

线程共享区域

  • StackOverflowError

    线程请求的栈深度大于虚拟机所允许的深度

  • OutOfMemoryError

    栈扩展时无法申请到足够的内存

     

在《Java虚拟机规范》中,对这个内存区域规定了两类异常情况:

本方法栈与虚拟机栈所发挥的作用类似,它们两个个区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈是描述本地方法运行过程的内存模型

本地方法栈(C栈)

  • StackOverflowError

    线程请求的栈深度大于虚拟机所允许的深度

  • OutOfMemoryError

    栈扩展时无法申请到足够的内存

     

在《Java虚拟机规范》中,对这个内存区域规定了两类异常情况:

这些数据类型在局部变量表中的存储空间以局部变量槽来表示,其中64位的long和double占用2个槽,其余的只占用一个,局部变量表在编译期间完成分配。

reutrn Address类型(指向了一条字节码指令的地址)

对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)

局部变量表存放了编译器可知的各种Java虚拟机基本数据类型(boolean byte char short int float long double)

补充:

每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈、动态连接、方法出口等信息,每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

虚拟机描述的是Java方法执行的线程内存的模型:

虚拟机栈内存的生命周期和线程相同。

虚拟机栈

 

生命周期:随着线程的创建而创建,随着线程的结束而销毁

程序计数器这个内存区域是唯一一个没有OutOfMemoryError情况的区域。

如果线程正在执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(Native)方法,这个计数器的值应该为空。

由于java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令,因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响、独立存储,我们称这类内存区域为"线程私有"的内存。

程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,字节码指示器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器、分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成

程序计数器

线程隔离区域

JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存

方法区
虚拟机栈本地方法栈程序计数器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值