jvm、gc、操作系统等基础知识总结

操作系统: 1,32位机器和64位机器 一、32位和64位是什么意思 1、32位和64位表示CPU一次能处理的最大位数; 2、32位CPU只能安装32位系统,64位CPU可以安装32位和64位系统; 3、如今市面上的CPU大多数为64位,怎么看CPU是32位还是64位。 二、寻址能力 1、32位系统的最大寻址空间是2的32次方=4294967296(bit)= 4(GB)左右,也就是说在32位机器整个操作系统中,所有应用程序加起来最多只能使用4g内存。 2、64位系统的最大寻址空间为2的64次方=4294967296(bit)的32次方,数值大于1亿GB; 3、也就是说32位系统的处理器最大只支持到4G内存,而64位系统最大支持的内存高达亿位数,实际使用过程中大多数的电脑32位系统最多识别3.5GB内存,64位系统最多识别128GB内存。

那么32位系统如何实现所谓超过4G内存破解,原理很简单,现在CPU基本都是64位处理器,也就是硬件是没有4GB的识别问题,破解就是应用64位系统寻址原理,来打开32位系统对超过4G内存识别限制。而将这些多出来内存则作为RAMDISK来使用,就是缓存盘,在WINDOWS 下软件运行都会产生临时文件,那么他就是将这些软件产品临时文件都搬到RANDISK上来,而不写入磁盘。在某个角度上来的确可以提高系统运行速度。但并不是真正系统和软件使用64位系统和软件寻址方式,仍然是32位的寻址。如果将这个破解应用在纯32位CPU上,你再试试看他能否能打开所谓超4G内存的破解。

数据在存储器(RAM)中存放是有规律的 ,CPU在运算的时候需要把数据提取出来就需要知道数据存放在哪里 ,这时候就需要挨家挨户的找,这就叫做寻址,但如果地址太多超出了CPU的能力范围,CPU就无法找到数据了。 CPU最大能查找多大范围的地址叫做寻址能力 ,CPU的寻址能力以字节为单位,如32位寻址的CPU可以寻址2的32次方大小的地址也就是4G,这也是为什么32位的CPU最大能搭配4G内存的原因,再多的话CPU就找不到了。

通常人们认为,内存容量越大,处理数据的能力也就越强,但内存容量不可能无限的大,它要受到系统结构、硬件设计、制造成本等多方面因素的制约,一个最直接的因素取决于系统的地址总线的地址寄存器的宽度(位数)。  计算机的寻找范围由总线宽度(处理器的地址总线的位数)决定的,也可以理解为cpu寄存器位数,这二者一般是匹配的。

Intel公司早期的CPU产品的地址总线和地址寄存器的宽度为20位,即CPU的寻址能力为2^20=10241024字节=1024K字节=1M字节;286的地址总线和地址寄存器的宽度为24位,CPU的寻址能力为2^24=1024410244B=410244KB=16M;386及386以上的地址总线和地址寄存器的宽度为32位,CPU的寻址能力为2^32=4096M字节=4G字节。 也就是说,如果机器的CPU过早,即使有很大的内存也不能得到利用,而对于现在的PⅡ级的CPU,其寻址能力已远远超过目前的内存容量。由此推出:地址总线为N位(N通常都是8的整数倍;也说N根数据总线)的CPU寻址范围是2的N次方字节,即2^N(B)

如果真正想有效让系统分配超过4G的内存,建议还是使用64位系统,只有64位系统才能原生支持超过4G内存的寻址。

参考:blog.csdn.net/fengyuwuzu0…

2,java一些字节位问题 一个中文字符不同编码使用的字节数: UTF-8编码长度:4 GBK编码长度:3 GB2312编码长度:3

一个英文字符不同编码使用的字节数: UTF-8编码长度:1 GBK编码长度:1 GB2312编码长度:1

1KB=1024B 1MB=1024KB 1GB=1024MB

对于2亿条评论内容占的空间大小,评价一条评价50字左右,一个汉字3个字节,2亿条评论,1GB=102410241024,需要的空间大于3gb

3,Java对象的表示模型和运行时内存表示

<1>Hotspot主要是用C++写的,所以它定义的Java对象表示模型也是基于C++实现的。 Java对象的表示模型叫做“OOP-Klass”二分模型,包括两部分:

  1. OOP,即Ordinary Object Point,普通对象指针。说实话这个名称挺难理解。说白了其实就是表示对象的实例信息
  2. Klass,即Java类的C++对等体,用来描述Java类,包含了元数据和方法信息等 <2>一个Java对象就包括两部分,数据和方法,分别对应到OOP和Klass。最简单的理解就是如果让你自己用Java语言来开发一套新的语言,你如何来表示这个新的语言的对象呢。肯定也是类似的思路,一个模块是用Java类来实现表示数据的部分,一个模块是用Java类实现表示方法和元数据的部分。 <3>JVM运行时加载一个Class时,会在JVM内部创建一个instanceKlass对象,表示这个类的运行时元数据。创建一个这个Class的Java对象时,会在JVM内部相应的创建一个instanceOop来表示这个Java对象。熟悉JVM的同学可以明白,instanceKlass对象放在了方法区,instanceOop放在了堆,instanceOop的引用放在了JVM栈 <4>在堆中创建的Java对象实际只包含数据信息,它包含三部分:
  3. 对象头,也叫Mark Word
  4. 元数据指针,可以理解为类对象指针,指向方法区的instanceKlass实例
  5. 实例数据 如果是数据对象的话,还多了一个部分,就是数组长度 对象头主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,偏向线程ID,偏向时间戳等。对象头的长度和JVM的字长一致,比如32位JVM的对象头是32位,64位JVM的对象头是64位。 这里可以看到,所谓的给一个对象加锁,其实就是设置了对象头某些位。当其他线程看到这个对象的状态是加锁状态后,就等待释放锁。 在方法区的instanceKlass对象相当于Class加载后创建的运行时对象,它包含了运行时常量池,字段,方法等元数据,当调用一个对象的方法时,如上面的图所示,实际定位到了方法区的instanceKlass对象的方法元数据。

参考:blog.csdn.net/iter_zc/art…

4,Java对象大小、对象内存布局及锁状态变化 Mark Word:存储对象运行时记录信息,占用内存大小与机器位数一样,即32位机占4字节,64位机占8字节 元数据指针:指向描述类型的Klass对象(Java类的C++对等体)的指针,Klass对象包含了实例对象所属类型的元数据,因此该字段被称为元数据指针,JVM在运行时将频繁使用这个指针定位到位于方法区内的类型信息。这个数据的大小稍后说 数组长度:数组对象特有,一个指向int型的引用类型,用于描述数组长度,这个数据的大小和元数据指针大小相同,同样稍后说 实例数据:实例数据就是8大基本数据类型byte、short、int、long、float、double、char、boolean(对象类型也是由这8大基本数据类型复合而成),每种数据类型占多少字节就不一一例举了 填充:补齐位置使得整体为8字节的整数倍,HotSpot的对齐方式为8字节对齐,即一个对象必须为8字节的整数倍,因此如果最后前面的数据大小为17则填充7,前面的数据大小为18则填充6,以此类推

说说元数据指针的大小。元数据指针是一个引用类型,因此正常来说64位机元数据指针应当为8字节,32位机元数据指针应当为4字节,但是HotSpot中有一项优化是对元数据类型指针进行压缩存储,使用JVM参数: -XX:+UseCompressedOops开启压缩 -XX:-UseCompressedOops关闭压缩 HotSpot默认是前者,即开启元数据指针压缩,当开启压缩的时候,64位机上的元数据指针将占据4个字节的大小。换句话说就是当开启压缩的时候,64位机上的引用将占据4个字节,否则是正常的8字节。

举例指针压缩 首先是Object对象的大小: 开启指针压缩时,8字节Mark Word + 4字节元数据指针 = 12字节,由于12字节不是8的倍数,因此填充4字节,对象Object占据16字节内存 关闭指针压缩时,8字节Mark Word + 8字节元数据指针 = 16字节,由于16字节正好是8的倍数,因此不需要填充字节,对象Object占据16字节内存

接着是字符'a'的大小: 开启指针压缩时,8字节Mark Word + 4字节元数据指针 + 1字节char = 13字节,由于13字节不是8的倍数,因此填充3字节,字符'a'占据16字节内存 关闭指针压缩时,8字节Mark Word + 8字节元数据指针 + 1字节char = 17字节,由于17字节不是8的倍数,因此填充7字节,字符'a'占据24字节内存

接着是整型1的大小: 开启指针压缩时,8字节Mark Word + 4字节元数据指针 + 4字节int = 16字节,由于16字节正好是8的倍数,因此不需要填充字节,整型1占据16字节内存 关闭指针压缩时,8字节Mark Word + 8字节元数据指针 + 4字节int = 20字节,由于20字节正好是8的倍数,因此填充4字节,整型1占据24字节内存

接着是字符串"aaaaa"的大小,所有静态字段不需要管,只关注实例字段,String对象中实例字段有"char value[]"与"int hash",由此可知: 开启指针压缩时,8字节Mark Word + 4字节元数据指针 + 4字节引用 + 4字节int = 20字节,由于20字节不是8的倍数,因此填充4字节,字符串"aaaaa"占据24字节内存 关闭指针压缩时,8字节Mark Word + 8字节元数据指针 + 8字节引用 + 4字节int = 28字节,由于28字节不是8的倍数,因此填充4字节,字符串"aaaaa"占据32字节内存

最后是长度为1的char型数组的大小: 开启指针压缩时,8字节的Mark Word + 4字节的元数据指针 + 4字节的数组大小引用 + 1字节char = 17字节,由于17字节不是8的倍数,因此填充7字节,长度为1的char型数组占据24字节内存 关闭指针压缩时,8字节的Mark Word + 8字节的元数据指针 + 8字节的数组大小引用 + 1字节char = 25字节,由于25字节不是8的倍数,因此填充7字节,长度为1的char型数组占据32字节内存

备注:开启指针压缩技术的前提条件:当jvm是64位且内存小于32G会默认开启指针压缩技术,那么按理说,4字节的最大寻址空间应该只有2的32次方,即4GB内存堆内存,明显不符合使用了,实际上不是这样的, 解释:并非如此, 由于对象是8字节对齐的, 因此对象起始地址最低三位总是0, 因此可以存储时可以右移3bit, 高位空出来的3bit可以表示更高的数值, 实际上, 可以使用指针压缩的maxHeapSize是4G * 8 = 32G.

参考:blog.csdn.net/lqp276/arti…

4,java如何计算一个对象的大小。 blog.csdn.net/bobpauline/… www.jianshu.com/p/9d729c9c9… blog.csdn.net/iter_zc/art…

public class ObjectSizeServiceTest { public static class Person { //private String name = "zhangsan"; private int age = 10; // private long age2 = 100; private double age3 = 20.3;

}

public static void main(String[] args) {
    //借助lucene的RamUsageEstimator,内部是基于Unsafe实现,实现了递归处理获取引用对象的大小
    Object object = new Object();
    Integer integerValue = 100;
    //System.out.println(RamUsageEstimator.sizeOf(integerValue));
    //System.out.println(RamUsageEstimator.sizeOf(object));
    Person person = new Person();
    System.out.println(RamUsageEstimator.sizeOf(person));
    System.out.println(getSizeByUnsafe(person));

}

/**
 * 对象头部的大小
 */
private static final int OBJECT_HEADER_SIZE = 8;
/**
 * 对象占用内存的最小值
 */
private static final int MINIMUM_OBJECT_SIZE = 8;
/**
 * 对象按多少字节的粒度进行对齐
 */
private static final int OBJECT_ALIGNMENT = 8;

// 获得Unsafe实例
//暂时不考虑对象的相互引用,还有超类,字符串,数组
public static long getSizeByUnsafe(Object object) {
    Unsafe unsafe;
    try {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        unsafe = (Unsafe) unsafeField.get(null);
    } catch (Throwable t) {
        unsafe = null;
    }
    Class kClass = object.getClass();
    long lastFieldOffset = -1;
    for (Field f : kClass.getDeclaredFields()) {
        if (!Modifier.isStatic(f.getModifiers())) {
            lastFieldOffset = Math.max(lastFieldOffset, unsafe.objectFieldOffset(f));
        }
    }
    if (lastFieldOffset > 0) {
        lastFieldOffset += 1;
        if ((lastFieldOffset % OBJECT_ALIGNMENT) != 0) {
            lastFieldOffset += OBJECT_ALIGNMENT - (lastFieldOffset % OBJECT_ALIGNMENT);
        }
        return Math.max(MINIMUM_OBJECT_SIZE, lastFieldOffset);
    }
    //该对象没有任何属性
    long size = OBJECT_HEADER_SIZE;
    if ((size % OBJECT_ALIGNMENT) != 0) {
        size += OBJECT_ALIGNMENT - (size % OBJECT_ALIGNMENT);
    }
    return Math.max(MINIMUM_OBJECT_SIZE, size);
}
复制代码

} 5,垃圾回收器 从线程运行情况分类有三种 串行回收,Serial回收器,单线程回收,全程stw; 并行回收,名称以Parallel开头的回收器,多线程回收,全程stw; 并发回收,cms与G1,多线程分阶段回收,只有某阶段会stw;

blog.csdn.net/zqz_zqz/art…

6,jvm内存分区 JVM的内存划分中,有部分区域是线程私有的,有部分是属于整个JVM进程;有些区域会抛出OOM异常,有些则不会,了解JVM的内存区域划分以及特征,是定位线上内存问题的基础。那么JVM内存区域是怎么划分的呢?

第一,程序计数器(Program Counter Register),在JVM规范中,每个线程都有自己的程序计数器(所以说这块空间是线程安全的)。这是一块比较小的内存空间,存储当前线程正在执行的Java方法的JVM指令地址,即字节码的行号。如果正在执行Native方法,则这个计数器为空。该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM情况的内存区域。(程序计数器是一个记录着当前线程所执行的字节码的行号指示器,JAVA代码编译后的字节码在未经过JIT(实时编译器)编译前,其执行方式是通过“字节码解释器”进行解释执行。简单的工作原理为解释器读取装载入内存的字节码,按照顺序读取字节码指令。读取一个指令后,将该指令“翻译”成固定的操作,并根据这些操作进行分支、循环、跳转等流程。 )

第二,Java虚拟机栈(Java Virtal Machine Stack),同样也是属于线程私有区域,每个线程在创建的时候都会创建一个虚拟机栈,生命周期与线程一致,线程退出时,线程的虚拟机栈也回收。虚拟机栈内部保持一个个的栈帧,每次方法调用都会进行压栈,JVM对栈帧的操作只有出栈和压栈两种,方法调用结束时会进行出栈操作。

该区域存储着局部变量表,编译时期可知的各种基本类型数据、对象引用、方法出口等信息。

第三,本地方法栈(Native Method Stack)与虚拟机栈类似,本地方法栈是在调用本地方法时使用的栈,每个线程都有一个本地方法栈。

第四,堆(Heap),几乎所有创建的Java对象实例,都是被直接分配到堆上的。堆被所有的线程所共享,在堆上的区域,会被垃圾回收器做进一步划分,例如新生代、老年代的划分。Java虚拟机在启动的时候,可以使用“Xmx”之类的参数指定堆区域的大小。

第五,方法区(Method Area)。方法区与堆一样,也是所有的线程所共享,存储被虚拟机加载的元(Meta)数据,包括类信息、常量、静态变量、即时编译器编译后的代码等数据。这里需要注意的是运行时常量池也在方法区中。根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。由于早期HotSpot JVM的实现,将CG分代收集拓展到了方法区,因此很多人会将方法区称为永久代。Oracle JDK8中已永久代移除永久代,同时增加了元数据区(Metaspace)。

第六,运行时常量池(Run-Time Constant Pool),这是方法区的一部分,受到方法区内存的限制,当常量池无法再申请到内存时,会抛出OutOfMemoryError异常。 在Class文件中,除了有类的版本、方法、字段、接口等描述信息外,还有一项信息是常量池。每个Class文件的头四个字节称为Magic Number,它的作用是确定这是否是一个可以被虚拟机接受的文件;接着的四个字节存储的是Class文件的版本号。紧挨着版本号之后的,就是常量池入口了。常量池主要存放两大类常量:

字面量(Literal),如文本字符串、final常量值 符号引用,存放了与编译相关的一些常量,因为Java不像C++那样有连接的过程,因此字段方法这些符号引用在运行期就需要进行转换,以便得到真正的内存入口地址。 class文件中的常量池,也称为静态常量池,JVM虚拟机完成类装载操作后,会把静态常量池加载到内存中,存放在运行时常量池。 第七,直接内存(Direct Memory),直接内存并不属于Java规范规定的属于Java虚拟机运行时数据区的一部分。Java的NIO可以使用Native方法直接在java堆外分配内存,使用DirectByteBuffer对象作为这个堆外内存的引用。

OOM可能发生在哪些区域上? 根据javadoc的描述,OOM是指JVM的内存不够用了,同时垃圾收集器也无法提供更多的内存。从描述中可以看出,在JVM抛出OutOfMemoryError之前,垃圾收集器一般会出马先尝试回收内存。 从上面分析的Java数据区来看,除了程序计数器不会发生OOM外,哪些区域会发生OOM的情况呢?

第一,堆内存。堆内存不足是最常见的发送OOM的原因之一,如果在堆中没有内存完成对象实例的分配,并且堆无法再扩展时,将抛出OutOfMemoryError异常,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”。当前主流的JVM可以通过-Xmx和-Xms来控制堆内存的大小,发生堆上OOM的可能是存在内存泄露,也可能是堆大小分配不合理。

第二,Java虚拟机栈和本地方法栈,这两个区域的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,在内存分配异常上是相同的。在JVM规范中,对Java虚拟机栈规定了两种异常:1.如果线程请求的栈大于所分配的栈大小,则抛出StackOverFlowError错误,比如进行了一个不会停止的递归调用;2. 如果虚拟机栈是可以动态拓展的,拓展时无法申请到足够的内存,则抛出OutOfMemoryError错误。

第三,直接内存。直接内存虽然不是虚拟机运行时数据区的一部分,但既然是内存,就会受到物理内存的限制。在JDK1.4中引入的NIO使用Native函数库在堆外内存上直接分配内存,但直接内存不足时,也会导致OOM。

第四,方法区。随着Metaspace元数据区的引入,方法区的OOM错误信息也变成了“java.lang.OutOfMemoryError:Metaspace”。对于旧版本的Oracle JDK,由于永久代的大小有限,而JVM对永久代的垃圾回收并不积极,如果往永久代不断写入数据,例如String.Intern()的调用,在永久代占用太多空间导致内存不足,也会出现OOM的问题,对应的错误信息为“java.lang.OutOfMemoryError:PermGen space”

内存区域 是否线程私有 是否可能发生OOM 程序计数器 是 否 虚拟机栈 是 是 本地方法栈 是 是 方法区 否 是 直接内存 否 是 堆 否 是 参考文章:www.cnblogs.com/QG-whz/p/96…

7,jvm调优 JVM中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m,linux系统自身一般占用1g的内存

<1>默认的新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定) 新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ),被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认,Edem : from : to = 8 :1 : 1 ( 可以通过参数–XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

<2>举例配置: java -Xmx3550m -Xms3550m -Xmn2g –Xss128k

-Xmx3550m:设置JVM最大可用内存为3550M。 -Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 -Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。 -Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

备注: -Xss规定了每个线程堆栈的大小。一般情况下256K是足够了。影响了此进程中并发线程数大小。 -Xms初始的Heap的大小。 -Xmx最大Heap的大小。 永久代 PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍(比如-XX:MaxPermSize=16m:设置持久代大小为16m,这个空间比较小)。 在很多情况下,-Xms和-Xmx设置成一样的。这么设置,是因为当Heap不够用时,会发生内存抖动,影响程序运行稳定性。

HotSpot虚拟机在1.8之后已经取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次full GC发生移动,比较消耗虚拟机性能。同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6 -XX:MaxPermSize=16m:设置持久代大小为16m。 -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

<3>假设给你4g内存空间的机器,你怎么设置堆内存 1,由于linux系统一般占用1g内存空间,因此系统分配1g 2,线上服务器一般是200个jvm活跃线程,每个线程栈的空间默认为1M,因此栈空间占据200m。 3,永久代一般在50m可以满足使用了。 4,程序计数器一般在20kb一下,因此这块空间可以忽略。 5,因此堆可用空间为2700mb,按照新生代和老年代1:2空间设置 第五点,我的理解:新生代需要频繁的youngc,并且还需要进行空间的压缩,如果太大,消耗的时间太长了,老年代不需要每次都压缩,因此在有碎片的同时,大内存可以放入更多的数据 备注:如果有常驻内存的数据,应该使用堆外内存,堆内空间虽然变小了,导致full gc上升,但是full gc的时间会变短(因此gc扫描的空间小了)

<6>堆外内存的好处是: (1)可以扩展至更大的内存空间。比如超过1TB甚至比主存还大的空间; (2)理论上能减少GC暂停时间,堆外内存会受gc的控制,之所以能减少gc暂停时间,如果把堆外内存的数据都放在堆内,每次gc都要全面扫描这块空间,如果放在堆外,gc只会扫描那些没有被引用的空间,因此常驻内存的本地缓存数据使用堆外内存更加合适。 (3)可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现; (4)它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数 (5)零拷贝提升数据的访问效率

堆外内存的释放过程: DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的, 它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了, 那将会把这个引用(Cleaner)放到java.lang.ref.Reference.pending队列里, 在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理, 而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类, 在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块

参考:www.cnblogs.com/andy-zhou/p…

8,Linux与JVM的内存关系分析 在一些物理内存为8g的server上,主要执行一个Java服务,系统内存分配例如以下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m。 Linux和Java NIO在内核内存上开辟空间给程序使用,主要是降低不要的复制,以降低IO操作系统调用的开销。比如,将磁盘文件的数据发送网卡,使用普通方法和NIO时。数据流动比較下图所看到的: 将数据在内核内存和用户内存之间拷贝是比較消耗资源和时间的事情,而从上图我们能够看到。通过NIO的方式降低了2次内核内存和用户内存之间的数据拷贝。这是Java NIO高性能的重要机制之中的一个(还有一个是异步非堵塞)。 从上面能够看出。内核内存对于Java程序性能也很重要,因此,在划分系统内存使用时候。一定要给内核留出一定可用空间。 参考:www.cnblogs.com/bhlsheji/p/…

9,cms参数配置和gc日志 blog.csdn.net/zqz_zqz/art…

10,gc调优 首先了解堆内存大体设置 最大堆设置,在单机web server的情况下,最大堆的设置建议在物理内存的1/2到2/3之间,如果是16G的物理内存的话,最大堆的设置应该在8000M-10000M之间。 Java进程消耗的总内存肯定大于最大堆设置的内存:堆内存(Xmx)+ 方法区内存(MaxPermSize,一般项目256M够用了)+ 栈内存(Xss,包括虚拟机栈和本地方法栈,默认配置1M的栈空间)*线程数 + NIO direct memory + socket缓存区(receive37KB,send25KB)+ JNI代码 + 虚拟机和GC本身+程序计数器(一般16kb,可以忽略不计)*线程数 = java的内存。

线程数一般指是业务线程+netty容器线程

举例,16GB内存机器,800MB(线程栈)+256MB(方法区)+1000MB(linux系统)+200MB(系统预留)+200MB(JNI代码),留给堆内存可分配空间在12GB以下

进行gc调优之前应该了解的基本知识 <1>在进行GC优化之前,需要确认项目的架构和代码等已经没有优化空间。我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过GC优化令其性能达到一个质的飞跃。 <2>通过上述分析,可以看出虚拟机内部已有很多优化来保证应用的稳定运行,所以不要为了调优而调优,不当的调优可能适得其反。 <3>最后,GC优化是一个系统而复杂的工作,没有万能的调优策略可以满足所有的性能指标。GC优化必须建立在我们深入理解各种垃圾回收器的基础上,才能有事半功倍的效果。 <4>gc优化两个核心的关注点就是:降低GC频率,缩短GC停顿时间。  在分代GC算法中,降低回收频率可以通过:(1) 降低对象分配/提升率;(2) 增加代空间的大小。减少新生代大小可以缩短新生代GC停顿时间,因为这样被复制到survivor区域或者被提升的数据更少

从微服务架构上的一些总结 <1>进行gc调优之前应该尽量思考原有的架构是否存在缺陷,代码有没有优化空间。 举例 ①假设你的服务在2000ms,那你jvm调参能有多大空间?因此这时候的着重点是在服务内部。 ②api项目往往都涉及很多服务的调用,如果内部都是串行化的执行逻辑导致耗时较长,会导致这些对象在young区驻留的时间较长(比如一次minor gc耗时50ms,api耗时100ms,因此在minor gc时api会延长50ms,99线就会被大幅增大,在gc的时候,eden区会向survivor迁移,如果api耗时优化在50ms以下,那服务的99线就会下降很多),因此api可以把一些逻辑并发异步化,使得api整体耗时降了下来,在降低均线的同时把99线降低。 这种场景通常指:minor gc的耗时和服务的响应时间很接近(短耗时的服务,比gc耗时稍长一点),因此gc会延长99线,使得均线和99线差距很大(比如均线5ms,99线在55ms),如果把服务响应优化在gc时间范围内,那么99线将会接近于均线。 ③如果项目存在大量的短生命周期对象,并且这些对象中很多数据都是冗余的,如果对项目进行压测,会出现大量的minor gc,甚至还出现大量major gc,服务性能压不上去,此时的做法可以有两种:一、加大堆内存的eden区空间,使得新生代的gc频率变的更低。二、优化依赖服务的架构,数据改成按需索取,减少短生命周期对象的占用新生代空间。 ④如果经常有内存泄漏发生,应该通过内存信息排查哪些内存没有被正常释放。 ⑤开启逃逸分析,减少堆内存空间的消耗(www.jianshu.com/p/20bd2e9b1… <2>GC优化一般步骤可以概括为:确定目标、优化参数、验收结果。 在互联网应用里面更多关注的是:低延迟。

优化案例1: Major GC和Minor GC频繁(导致99线高居不下,api压测期间,项目出现大量的young gc,old gc) api项目通常都是一些大量的短生命周期对象,因此年轻代应该更大一些。原因:如果eden区满了,会向survivor迁移(数据的复制成为影响服务99线的核心因素),年轻代设置更大一些,eden对象在短周期被销毁,不用迁移,因此99线更低。service项目通常会有一些本地缓存数据,因此老年代更加大一些,甚至可以使用堆外内存代替老年代,减少full gc频率。

优化案例2:请求高峰期发生GC,导致服务可用性下降(服务超时很多) GC日志显示,高峰期CMS在重标记(Remark)阶段耗时1.39s。Remark阶段是Stop-The-World(以下简称为STW)的,即在执行垃圾回收时,Java应用程序中除了垃圾回收器线程之外其他所有线程都被挂起,意味着在此期间,用户正常工作的线程全部被暂停下来,这是低延时服务不能接受的。本次优化目标是降低Remark时间。

优化参考根据:cms在标记对象是否可达,实际上是要从新生代开始向老年代扫描的(因为有一些对象会跨代引用),因此一般在cms在并发标记之前进行一次minor gc,但是在重新标记之前,新生代同样会产生大量的对象,因此在重新标记时,如果不再进行一次minor gc,会增加remark的stw时间,CMS提供CMSScavengeBeforeRemark参数,用来保证Remark前强制进行一次Minor GC。(优化案例减少200ms的延迟)

整体参考:www.jianshu.com/p/af2e21258… blog.csdn.net/zhoudaxia/a… blog.csdn.net/u022726695/… www.cnblogs.com/qmfsun/p/53… www.cnblogs.com/hirampeng/p… blog.csdn.net/u010556151/… 11,延迟(latency)和吞吐量(throghtput)

延迟和吞吐量,是衡量软件系统的最常见的两个指标。 延迟一般包括单向延迟(One-way Latency)和往返延迟(Round Trip Latency),实际测量时一般取往返延迟。它的单位一般是ms、s、min、h等。 吞吐量一般指相当一段时间内测量出来的系统单位时间处理的任务数或事务数(TPS)。注意“相当一段时间”,不是几秒,而可能是十几分钟、半个小时、一天、几周甚至几月。它的单位一般是TPS、每单位时间写入磁盘的字节数等

低延迟一定意味着高吞吐量吗?如果不是,试举出反例。 举例:飞机和火车在一段时间内的运货量,飞机的速度很快,但是在较长一段时间吞吐量远比不上火车。

参考:blog.csdn.net/lkx94/artic…

12,cpu的load比较高的原因排查 <1>首先定位是不是jvm线程消耗的cpu比较高(top命令),如果是,那就根据pid,即是进程id。 <2>查看进程的所有线程对应的cpu时间片大小(top命令)。 <3>jstack 查看指定线程id的上下文堆栈信息,根据堆栈信息去分析代码,确定是不是业务逻辑代码有问题。 <4>配合查看gc log,确定是否出现频繁的major gc,导致cpu消耗急剧上升(如内存泄漏,导致可用堆空间下降,导致gc频繁出发,gc时会并发使用多核cpu,导致load比较高)。

参考demo:比如多线程爬第三方数据,对方响应的数据是大对象,由于网络io是流传输的,我是一次调用完,写到内存才进行解析的,因此young区很快写满了,进而晋升到old区,结果内存还是不够,接着继续old gc,这时候cpu使用立马飙升,电脑迅速发热。

参考:blog.csdn.net/u012448083/…

转载于:https://juejin.im/post/5cac1349f265da037d4f8864

你可能感兴趣的