1、高性能的硬件上的程序部署策略
1.1、目前主要有两种方式:
1.1.1、通过64位JDK来使用大内存
利:可以减少FUll GC对频率
弊:内存回收导致的长时间停顿、相同的程序在64位的JDK中消耗的内存普遍高于32位的JDK(由于指针膨胀和数据类型对齐填充等因素导致)、需要保证程序足够稳定,因为这种应用如果产生了堆溢出几乎无法产生堆转存储快照(因为要产生十几GB乃至更大的Dump文件),就算产生了快照几乎无法进行分析;
1.1.2、使用若干个32位虚拟机建立逻辑集群来利用硬件资源
具体的做法就是:一台物理机器上启动多个应用服务进程,每个服务进程分配不同的端口,然后在搭配一个负载均衡器,用反向代理的方式来分配访问请求;
利:可以充分的利用物理资源
弊:1)尽量避免节点进行全局资源的竞争,例如磁盘的竞争,如果各个节点同时访问同一个磁盘的话,很容易导致IO异常
2)很难最高效的利用一些资源,例如连接池,每个节点都有自己的连接池,会存在一些节点连接池满了,而一些节点的连接池还有空闲
3)各个节点受到32位的内存限制,在32位的window操作系统中,每个进程只能使用2GB的内存(一般不到2GB,还有堆外内存的开销),在某些Linux或者Unix系统(Solaris)中可以提升到4GB,(2~32)内存限制;
1.2、问题记录:
为什么32位机器上有最大4GB内存限制?网上的详细解说:
32位系统最大只能支持4GB内存之由来 - Matrix海子 - 博客园
2、集群间同步导致的内存溢出
在某些集群同步数据的时候,存在信息传输失败需要重新发送请求的可能,一些同步机制中,在确保所有注册的节点都同步完成,并发送回了响应信息前,这些同步数据就会保存在内存中;由于集群间数据同步过于频繁,当网络情况不能满足传输的要求时,重发的数据在内存中不断的堆积,就会发生内存溢出;
3、堆外内存导致的内存溢出
3.1、内存分析:
在JDK8中引入了NIO的同时引入了Direct Memory的概念,Direct Memory并不会占用堆内存,但是受到总的物理内存的限制,例如:在32位的window操作系统后中,只能分配了2GB内存,堆内存占用了1.6GB,那么Direct Memory就只能占用0.4GB的内存;在写IO程序(Netty),经常使用ByteBuffer来读取写入数据(基于通道channel和缓存Buffer)的IO方式,可以使用Native本地函数库直接使用堆外内存,然后使用Java堆中的DirectByteBuffer对象作为操作这块内存的引用;避免了Java堆和native堆的来回切换,从而提升性能;
如果我们不断的使用对外内存,堆内存很少使用,并且配置了-XX:+DisableExplicitGC,JVM就不会进行GC,DirectByteBuffer就不会被回收,当需要分配堆外内存的时候,就没有堆外内存空间可供分配,就会抛出OutOfMemoryError;
配置直接堆外内存大小参数:-XX:MaxDirectMemorySize;
3.2、除了DirectMemory会占用内存外,还有:
线程堆栈:-Xss,当纵向无法分配,就是无法分配新的栈帧就会抛出StackOverflowError;无法横向分配,就是无法建立新的线程,就会抛出OutOfMemoryError:unable to create new native thread;
Socket缓存区:每一个Socket连接都会有Receive(37KB)和Send(25KB)两个缓冲区,如果连接多的话就会占用较大的内存;IOException: Too mang open files 异常
JNI代码:如果代码中使用了JNI调用本地库,这部分占用的内存也不在堆内存中
虚拟机和GC:虚拟机和GC代码执行的时候也要消耗内存;
3.3、问题记录:
堆外内存导致的溢出错误,使用-XX:+DisableExplicitGC开关,会导致System.gc()变成空调用,没有任何对作用?网上的详细解说:
-XX:+DisableExplicitGC弊端_pocher的博客-CSDN博客_disableexplicitgc
4、外部命令导致内存溢出
在代码中使用Runtime.getRuntime().exec()方法调用一个外部shell脚本命令;Java虚拟机在执行这个命令,需要先克隆一个和当前虚拟机拥有一样环境变量的进程,再使用这个进程去执行这个命令,最后退出;这个过程是非常消耗资源的,虽然命令本身执行的速度很快,但是频繁的调用的话,系统的消耗是很大的,CPU和内存的消耗负担都很大;
5、服务器JVM进程崩溃
远程调用进行数据传输,由于传输数据量大,传输频繁,网络拥堵,两边的服务器速度不对等,时间越长,导致在等待的线程和Socket连接越来越多,超过系统能承受的能力,就会使得虚拟机进程崩溃;可以使用消息队列进行异步调用;
6、不恰当的数据结构导致内存的占用过大
6.1、基础数据占用内存情况:
byte\boolean : 1字节
short\char: 2字节
int\float: 4字节
long\double: 8字节
6.2、java对象:对象头,实例数据、对齐填充
6.2.1 对象头占用空间
1. 在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。
2. 在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。
3. 在64位开启指针压缩的情况下 -XX:+UseCompressedOops,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。
4. 如果对象是数组,那么额外增加4个字节(对象头中必须有一块数据用于记录数组长度,也就是一个int类型的对象,占4字节。)
6.2.2 实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
6.2.3 对齐填充
最后一块对齐填充空间并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。这是由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。