JVM性能调优:基本概念介绍

JVM的运行数据区

在讲解之前我们很有必要先了解一下JVM的结构,下面这张图就能简单呈现出 JVM 的结构原理,见下图。

JVM结构原理

由上图我们可以清楚看到在JVM运行数据区一共有5大部分,分别是方法区、堆、栈、寄存器、本地方法栈。下面我将对这5大做简单说明,以帮助大家更好的理解。

方法区

方法区不仅仅是存储“方法”,它存储的是整个Java class文件的信息,我们知道当JVM运行时,类加载器子系统将会从Java class文件中提取类信息,然后把这些class信息保存到该区域。比方说Class name、Class type(enum, class, interface)、field、method等等数据信息。

堆( Heap)

在Java中每一个应用程序都唯一对应着一个JVM的实例,每一个JVM实例又有且仅对应一个堆内存。关键字new出来的对象实例、this指针、数组等都是存储在堆中的,并且这些数据所有应用都会进行线程共享。堆内存由JVM虚拟机垃圾回收器GC自动内存管理机制进行管理。

栈( Stack)

栈是操作系统内核给线程建立起来的存储区域,这里面会保存这在线程中进行方法调用的状态数据,它具备列队先进后出的特点。栈中的数据的大小和生命周期都是确定的,比如一个方法中公共基本数据类型int就是存储在栈(stack)中的,而这个int的大小是固定的,当这个方法结束退出后这个int变量的生命周期也就结束了。在栈(stack)中每一Java 方法都对应一个栈帧,JVM会对栈进行压栈、出栈这两种操作,栈帧便是这两种操作的基本单位。

寄存器

寄存器与线程是对应的,一个线程必然会对应一个寄存器,它里面存储的就是一条指令的地址。

本地方法栈

在Java中native关键标注的后就可以用来调用其他语言编写的程序,比如用C/C++编写的本地程序代码,而这些方法就是在本地方法栈中运行的,而不是在Java栈中运行,这一点一定要注意区分。

JVM G1源码分析和调优

京东官方店旗舰店

¥89

购买

数据类型

Java数据类型有两类:基本类型和引用类型。

基本类型的变量保存原始值,即:他代表的值就是数值本身;引用类型的变量保存引用值,“引用值”表示某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。基本类型包括:byte | short | int | long | char | float | double | boolean。引用类型包括:类类型,接口类型和数组。堆与栈

程序要顺利运行,堆和栈内存尤其关键,所以我们必须先把这两者的关系将清楚,否则就会存在一知半解、懵懵懂懂的状态。下图就简单的描述了堆、栈的状态。

栈是运行时的单位,堆是存储的单位

程序是怎么执行的,数据是怎么处理的,这些都是栈的工作职责,也就是说栈解决了程序的运行问题;程序中的数据要存放在哪里,怎么存放这些问题就是堆的工作职责,也就是说对解决了数据存储的问题。

每一个线程执行的业务逻辑都是不一样的,所以每一个独立线程都会有一个独立的线程栈,在Java中一个线程会有一个与之对应的线程栈。而在堆中存储的数据是多有线程共享的。

栈里面存储的数据都是跟当前线程(或程序)相关的,包括局部变量、程序运行状态、方法返回值等等;堆只负责存储对象信息。

堆、栈都可以存储数据,那么把两者分开又是基于何种考虑呢?

栈代表的是处理逻辑,对代表的是数据,从软件工程的设计角度来看分开处理使得逻辑更加清晰,分而治之,隔离、模块化在软件设计的各方各面都有很好的体现。将堆和栈进行分离,也使得堆中的内容可以被多个线程栈所共享。这样做的的好处是:一方面共享内存,提供了有效的数据交互方式,另一方面堆中数据可以被所有线程栈共享也节省了空间。分开也是因为两者结构导致。栈在程序运行的时候需要,像保存系统运行时的上下文,地址段的划分等等。栈的数据结构导致了只能向上增长,这就限制了存储数据的能力。而对不同,对内存是可以根据需要动态增长的,所以堆、栈分开就实现了动态增长,相应的在栈中只需要存储堆中的一个地址引用就可以了。Java是完全面向对象的,这就是堆和栈的完美结合。其实我们将对象解剖后发现:对象的属性就是数据,放在堆中;对象的方法就是运行逻辑,放在栈中。建立一个对象我们既设计出了数据结构,也编程了数据的处理逻辑,我们有理由相信面向对象的程序确实相当完美。Java对象的大小

基本数据的类型的大小都是固定的,非基本类型的Java对象,其大小就不一定了,会根据各自的具体情况而有所不同。

在Java中,当我们new一个空Object对象,把被存储在堆中,其大小大概是8byte。如下:

Object obj = new Object();

它所占的空间大小为:4byte+8byte。4byte是栈中存储对象引用所需要内存,8byte是堆中对象的数据内存。

Object类作为其他所有的父类,所有不管什么样的Java对象,它的大小都是肯定大于8byte的,这一点毋庸置疑。

基于Object对象的大小,我们就可以计算出其他对象的大小。

计算公式:空对象(8byte) + int(4byte) + Boolean(1byte) + 空Object对象引用(4byte) = 17byte。

温馨提示:由于Java对对象内存分配时都是以8的整数倍来分,故大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。

需要注意基本类型的包装类型要把他们以对象来看待。包装类型的大小至少是12byte,而这12byte没有包含任何有效信息,同时,Java对象大小是8的整数倍,故一个基本类型包装类的大小至少是16byte。

引用类型

对象引用类型分为强引用、软引用、弱引用和虚引用。

强引用:声明对象是虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收;

软引用:一般作为缓存使用。在垃圾回收时,虚拟机会根据当前系统的内存情况决定是否对软引用进行回收。

提示:虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:与软引用类似,都是作为缓存使用。在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

Java中参数传递时传值还是传引用?

要正确理解这个问题,我们必须先了解一下两点:

Java中没有指针的概念程序运行是在栈中进行的,因此参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。明确了上面2点,我们就知道Java中都是进行传值调用。这是重点,一定要紧记!

传引用的错觉是如何造成的呢?

在栈中运行时,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。

对象是由基本类型组成的。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。

栈是程序运行最根本的。没有堆程序可以运行,但是没有栈程序是肯定运行不了的。堆是为栈进行数据存储服务,堆是共享内存,所有线程栈都可以调用。

Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。

Java虚拟机中对象的访问及存放

Student stu = new Student();

这上面的代码实例中Student stu是一个引用变量,所以存储在栈中,new Student()是一个实例对象存储在堆中。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

由于reference 类型在Java 虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java 堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。

句柄方式堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示。

指针方式堆中对象的布局中就必须考虑如何放置访问类型

这两种访问方式有什么区别?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值