通常来说Java平台标准版(Java SE)包括 Java SE开发工具包(JDK)和Java SE运行时环境(JRE)。
JRE提供了运行以Java编程语言编写的applet和应用程序所必需的库,Java虚拟机和其他组件;JDK包括JRE以及编译器和调试器等命令行开发工具,可以用来开发Java应用程序 。
虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务,在 HotSpot 虚拟机中直接将虚拟机栈和本地方法栈合二为一。
1)对象创建
在java程序中,创建对象的方式有多种。最常用的方式是new语句,还可以通过反射机制、Object.clone方法、反序列化以及Unsafe.allocateInstance方法来新建对象。
(1)类加载过程
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
new对象创建过程:
(2)内存分配
对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
指针碰撞:
- 场景:Java堆中内存是绝对规整的;
- 原理:所有用过的内存都放在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,分配内存时只需要把那个指针向空闲空间那边挪动一段与对象大小相等的距离就可以了;
- GC收集器:Serial、ParNew等带Compact过程的收集器。
空闲列表:
- 场景:Java堆中内存不是规整的;
- 原理:虚拟机会维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录;
- GC收集器:CMS基于Mark-Sweep算法的收集器。
在Hotspot虚拟机里,对象在堆里的存储布局主要分为三部分对象头,实例数据和对象填充。
2)对象访问
Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种。
(1)使用句柄
Java堆中将会划分出一块内存来作为句柄池,reference
中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
(2)直接指针
Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
应用程序的内存分配:
堆的详细结构:
-XX:MaxTenuringThreshold
设置)Full GC触发机制:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
Major GC是清除老年代,往往Major GC发生一次就会发生Minor GC,所以也叫FULL GC。FULL GC 的速度往往会比 Minor GC 慢 10 倍。
JVisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈。
demo1
public class HeapTest {
byte[] a = new byte[1024 * 100]; //100kb
public static void main(String[] args) throws Exception{
ArrayList heapTests = new ArrayList<>();
while(true){
heapTests.add(new HeapTest());
Thread.sleep(10);
}
}
}
运行程序跟工具:
note 插件安装:
地址:VisualVM: Plugins Centers
(1)选择版本
(2)选择插件下载
(3)安装插件
将“GC Roots”对象作为起点从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象
GC Roots根节点: 线程栈的本地变量、静态变量、本地方法栈的变量等等
Arthas 是阿里巴巴开源的一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
运行后会列出所有的进程
常用命令:
1)dashboard,按
ctrl+c
可以中断执行
2)thread 1会打印线程 ID 1 的栈,通常是 main 函数的线程。
3) jad 来反编译
4)其他命令
命令列表 | arthas
什么是垃圾?
一个对象没有任何引用指向他,那他就认作是需要回收的垃圾对象。
如何寻找垃圾对象?
1)引用计数法:对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的数量。对象的引用计数器的值为0,即表示对象A不能在被使用,可进行回收。
缺点:(1)他需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
(2)每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
(3)引用计数器还有一个严重的问题,即无法处理循环引用的问题,这是一条致命的缺陷,导致在Java回收的垃圾回收器中没有使用这类算法。
2)GC roots 可达性分析算法:
根可达:判断一个对象到 Gc Roots这些根对象之间存不存在引用链,存在的话,不回收,不存在的话,回收掉即可。
GC Roots 根对象:
1、虚拟机栈中引用的对象;开发人员写的方法里面的变量指向的对象;比如:Deque
numbers = new LinkedList<>();
2、方法区中的静态属性引用的对象;类中定义的静态的类变量引用的对象;例如:private static String s1 = "Java";
3、本地方法栈中引用的对象,native 方法里面的变量指向的对象
4、Java 虚拟机内部的引用,比如基本数据类型对应的 Class 对象; JVM 里面常驻的异常对象,比如空指针异常 NullPointException;
5、被同步锁 synchornized 持有的对象;
如何清楚垃圾对象?
1)Mark-Sweep (标记清除)
新生代的垃圾回收器共有三个:Serial,Parallel Scavenge 和 Parallel New。这三个采用的都是标记 - 复制算法。
老年代的垃圾回收器也有三个:Serial Old 和 Parallel Old,以及 CMS。
G1(Garbage First)是一个横跨新生代和老年代的垃圾回收器。
java 11 引入了 ZGC,宣称暂停时间不超过 10ms。
1)Serial
2)ParNew
ParNew收集器是Serial收集器的多线程版本;除了使用了多线程进行垃圾收集以外,其他的都和Serial一致;它默认开始的线程数与CPU的核数相同,可以通过参数-XX:ParallelGCThreads
来设置线程数。
3)Parallel Scavenge
Parallel Scavenge收集器依然是个采用复制算法的多线程新生代收集器,它与其他的收集器的不同之处在于它主要关心的是吞吐量。
吞吐量=用户线程执行时间/(用户线程执行时间+垃圾收集时间)
4)Serial Old
5)Parallel Old
6)CMS
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,在互联网网站、B/S架构的中常用的收集器就是CMS。
缺点:
5)Garbage First (G1 回收器)
G1是一款面向服务端应用的垃圾回收器。
参数 | 描述 |
---|---|
-XX:+UseSerialGC | 启用串行收集器 |
-XX:+UseParallelGC | 启用并行收集器,配置了该选项,那么 - XX:+UseParallelOldGC 默认启用 |
-XX:+UseParallelOldGC | FullGC 采用并行收集器,默认禁用,如果设置 - XX:+UseParallelGC 则启用 |
-XX:+UseParNewGC | 年轻代采用并行收集器,如果设置了 - XX:+UseConcMarkSweepGC,自动启用 |
-XX:ParallelGCThreads | 年轻代及老年代垃圾回收使用的线程数,默认是 JVM 使用的 CPU 个数 |
-XX:+UseConcMarkSweepGC | 对于老年代,启用 CMS 垃圾收集器,当并行收集器无法满足应用的延迟需求时,推荐使用 CMS 或 G1 收集器,启用该选项后,-XX:+UseParNewGC 自动启用;可以使用-XX:ConcGCThreads设置并发线程数量。 |
-XX:+UseG1GC | 启用 G1 收集器,G1 是服务器类型的收集器,用于多核,大内存的机器,它在保持高吞吐量的情况下,高概率满足 GC 暂停时间的目标 |
-XX:+PrintGC |
输出GC日志 |
-XX:+PrintGCDetails | 输出GC的详细日志 |
-XX:+PrintGCTimeStamps | 输出GC的时间戳(以基准时间的形式) |
-XX:+PrintGCDateStamps | 输出GC的时间戳 |
-XX:+PrintHeapAtGC | 在进行GC的前后打印出堆的信息 |
-Xloggc:../logs/gc.log | 日志文件的输出路径 |
JVM调优主要是优化FULL GC:
参数 | 参数作用 | 初始值 |
---|---|---|
-server | 启动 Server,以服务端模式运行 | |
-Xms | 最小堆内存 | 物理内存的1/64(<1GB) |
-Xmx | 最大堆内存 | 物理内存的 1/4(<1GB) |
-Xmn | 设置年轻代大小(for 1.3/1.4),注意:此处的大小是(eden+ 2 survivor space)。与 jmap -heap 中显示的 New gen 是不同的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8 | |
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | |
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | |
-Xss | 每个线程的堆栈大小 | |
-XX:MetaspaceSize | 元空间初始值 | |
-XX:MaxMetaspaceSize | 元空间最大内存 | |
-XX:MaxNewSize | 新生代最大内存 | |
-XX:NewRatio | 年轻代和老年代大小比值,取值为整数,默认为 2 | |
-XX:SurvivorRatio | Eden 区与 Survivor 区大小比值,取值为整数,默认为 8 |
Tomcat 常见的几个 JVM 异常:
-Xms512m -Xmx2048m
-XX:PermSize=50m -XX:MaxPermSize=50m
检查代码是否陷入死循环,导致内存溢出
如果并发量很高的情况,光靠分布式架构是解决不了的,我们还得集成一些高性能的中间件来提高系统的吞吐量。常见的中间件:
序号 | 中间件 | 解决什么问题 |
---|---|---|
1 | Redis | 分布式缓存 |
2 | ActiveMQ、RabbitMQ、Kafka、RocketMQ | 消息队列 |
3 | Solr、ElasticSearch | 全文检索 |
4 | Elastic-Job、Xxl-Job | 分布式定时器 |
5 | Netty | 高性能通讯框架,比如:开发一个百万级推送系统、弹幕系统 |
6 | MongoDB、Hbase | nosql数据库,缓解MySQL数据库的压力 |
7 | MyCat、Sharding-JDBC | MySQL数据库的分库分表 |
1)HashMap、HashTable、ConcurrentHashMap的区别
Map map=new HashMap<>(); //线程不安全
Map map=Collections.synchronizedMap(new HashMap());//线程安全
2)在海量元素中(例如:10亿无序、不定长、不重复)快速判断一个元素是否存在
3)IO合理使用
4)并发编程
5)SQL语句性能优化
6)场景设计思路
(1)案例1:如果用户下单成功,15分钟未付款,自动取消订单
(2)案例2:比如登录的时候通过短信验证码进行验证,60s只能不能重复发送,验证码有效期5分钟
(3)案例3:从Excel读取并且导入100万条数据
性能提升方案: