Spring
SpringBoot
SpringMVC
SpringCloud
Mybatis
RabbitMQ
JVM
Spring
Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的容器框架,简化Java开发,低耦合,方便集成其他框架(Mybatis等)。
一、控制反转(IOC)
也称为依赖注入(DI),获得依赖对象的过程由自己创建或查找变成了由容器主动注入,依赖对象的创建销毁都由容器来管理,实现对象间解耦。
反射机制:在运行状态中,对于任意一个类,都能获取这个类的所有属性和方法,对于任意一个对象,都能调用对象中的任意属性和方法。
常用方法:
1.实例对象.getClass(),获取Class对象,仅用于引用类型;
2.className.class,当前没有某个类的对象时,获取Class对象,引用、基本类型都可以;
3.Class.forName(String className),全路径名,获取Class对象,不需要导入类包,解耦。
二、Bean的作用域与生命周期
Spring中,组成应用程序的主体及IOC容器管理的对象称为Bean。
五种作用域
1.singleton单例模式,默认单例模式;
2.prototype原型模式(每次获取Bean都返回新的实例);
3.request(每次HTTP请求都创建新的实例,仅适用于Spring Web应用中);
4.session(同一个HTTP Session共享一个Bean实例,仅适用于Spring Web应用中);
5.globalSession(Portlet应用,仅适用于Spring Web应用中)。
生命周期
1.Spring IOC容器找到bean的定义并实例化,默认单例模式;
2.Spring IOC容器对bean进行依赖注入;
3.如果bean实现了xxxAware接口,Spring将相关的xxxAware实例注入给bean;
4.如果bean实现了BeanPostProcessor接口,将调用postProcessBeforeInitialization方法;
5.如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;
6.如果bean实现了BeanPostProcessor接口,将调用postProcessAfterInitialization方法;
7.此时bean已经准备就绪,可以被应用程序使用了,bean将一直驻留在应用上下文中,直到该应用上下文被销毁;
8.若bean实现了DisposableBean接口,Spring将调用distroy方法,同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用。
三、事务
事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态。
四个特性ACID
1.原子性(Atomicity):事务是一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用。
2.一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败,在现实中的数据不应该被破坏。
3.隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
4.持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来,通常情况下,事务的结果被写到持久化存储器中。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring并不直接管理事务,具体的平台实现接口
实现方式
1.编程式:调用getTransaction(),commit(),rollback()方法
2.声明式:基于TransactionProxyFactoryBean
3.声明式:@Transactional注解
Spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。
事务隔离
事务隔离级别定义了一个事务可能受其他并发事务影响的程度。
并发事务可能出现的问题:
1.脏读:一个事务读取了另一个事务改写但尚未提交的数据;
2.不可重复读:一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据;
3.幻读:一个事务修改了几行数据,接着另一个并发事务插入了一些数据,在随后的查询中,第一个事务就会发现有仍未修改的记录。
四、AOP
面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等,是OOP(面向对象编程)的补充和完善。
OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与对象的核心功能毫无关系,安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切,在OOP设计中,它导致了大量代码的重复,且不利于各个模块的重用。
@Aspect声明切面类
@Compoment将转换成容器中的bean或者是代理bean
@PointCut确定切入点的位置,execution匹配连接点
@Before在切入点方法执行之前执行定义的增强通知(advice)
@AfterReturning在目标方法正常完成后把增强处理织入
@AfterThrowing在异常抛出后织入的增强
执行顺序
无异常:@Around→@Before→方法执行→@Around→@After→@AfterReturning
有异常:@Around→@Before→扔异常ing→@After→@AfterThrowing
多个Aspect类,通过@Order(n)设置优先级,值越小越先执行。
如果有类中自调用方法的情况,在SpringBoot启动类上标记这个注解@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true),内部调用方法时采用((ClassName)AopContext.currentProxy()).MethodName();方式
AOP调用者类名为sun.reflect.NativeMethodAccessorImpl
SpringBoot
SpringBoot简化了Spring中大量的配置文件,是服务于框架的框架。
一、常用注解(annotations)
1.@SpringBootApplication:让SpringBoot自动给程序进行必要的配置,是@ComponentScan、@SpringBootConfiguration以及@EnableAutoConfiguration的集合,新建工程后,*Application的入口类就自动添加了这个注解。
2.@MapperScan:SpringBoot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis接口的扫描。
3.@Autowired:自动导入依赖的Bean,把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作,当加上(required=false)时,就算找不到bean也不报错。
4.@Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定,与@Autowired配合使用,@Qualifier限定描述符除了能根据名字进行注入,也能进行更细条件的指定。
5.@Service:用来标记业务层的组件,我们会将业务逻辑处理的类都加上这个注解交给Spring容器。
6.@Repository:@Repository注解类作为DAO对象,管理操作数据库的对象,例如Mybatis的Mapper接口。
7.@Controller:用于定义控制器类,和@RequestMapping来配合使用拦截请求,如果不在method中注明请求的方式,默认是拦截get和post请求,这样请求完成后会转向一个视图解析器。但是在大多微服务搭建的时候,前后端会做分离,后端只关注数据处理,返回json数据的话,需要配合@ResponseBody注解来完成。
8.@RequestMapping:提供路由信息,负责URL到Controller中的具体函数的映射,方法返回值通常解析为跳转路径。
9.@ResponseBody:该注解一般会配合@RequestMapping一起使用,返回结果不会被解析为跳转路径,而是直接写入HTTP response body中,比如异步获取json数据,会直接返回json数据。
10.@RestController:是@Controller和@ResponseBody的结合,一个类被加上@RestController注解,数据接口中就不再需要添加@ResponseBody,更加简洁。
11.@Component是通用注解,其他三个注解(@Service, @Controller, @Repository)是这个注解的拓展,并且具有了特定的功能。
12.@Transactional:一般在servcie层添加事务注解,即可开启事务。
二、热部署
SpringBoot热部署就是在项目正在运行的时候修改代码, 却不需要重新启动项目。
1.使用springloaded
2.使用spring-boot-devtools,鼠标停留在pom中, ctrl+shift+alt+/,点击Registry,然后勾选compiler.automake.allow.when.app.running
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
IDEA要在setting中配置
SpringMVC
SpringMVC是基于Spring的Web框架,
MVC:b/s模式,把Model-View-Controller分离,实现各模块独立编程。
一、组件
1.DispatcherServlet:中央控制器,把请求给转发到具体的控制类
2.Controller:具体处理请求的控制器
3.HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略
4.ModelAndView:服务层返回的数据和视图层的封装类
5.ViewResolver:视图解析器,解析具体的视图
6.Interceptors:拦截器,负责拦截我们定义的请求然后做处理工作
二、运行流程
1.用户向服务器发送请求,请求被DispatcherServlet捕获;
2.DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
3.DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter(如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(...)方法);
4.提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller),在填充Handler的入参过程中,根据配置,Spring将做一些额外的工作:
HttpMessageConveter:将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换,如String转换成Integer、Double等
数据格式化:对请求消息进行数据格式化,如将字符串转换成格式化数字或格式化日期等
数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5.Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象;
6.根据返回的ModelAndView,选择一个适合的ViewResolver返回给DispatcherServlet;
7.ViewResolver结合Model和View,来渲染视图;
8.将渲染结果返回给客户端。
SpringCloud
SpringCloud是致力于分布式系统、云服务的框架。
Mybatis
ORM:对象关系映射,是指将数据库中的每一行数据用对象的形式表现出来;
JPA:Java持久化接口,它是JavaEE关于ORM思想的一套标准接口,仅仅是一套接口,不是具体的实现;
Mybatis是一个实现了JPA规范的用来连接数据库并对其进行增删改查操作的开源框架,是一个基于SQL开发的半ORM组件。
一、基础
1.#{}和${}的区别
->#{}是预编译处理,Mybatis会将其替换为?号,调用PreparedStatement的set方法来赋值,可有效防止SQL注入,提高安全性;
->${}是字符串替换,Mybatis会将其替换为常量的值。
2.<resultMap>
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系,当实体类中的属性名和表中的字段名不一样或者字段数量不一样时可使用<resultMap>,
用id属性来映射主键字段,用result属性来映射非主键字段,property为实体类属性名,column为数据表中的字段名
3.模糊查询like
在代码中添加sql通配符"%xxx%"
4.工作原理
通常一个xml映射文件,都会写一个Dao接口与之对应,接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
5.mapper接口调用时的要求
1.Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
2.Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同;
3.Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
4.Mapper.xml文件中的namespace即是mapper接口的类路径。
6.批量插入
Oracle
<insert id="xxx" parameterType="java.util.List">
insert into xxx (...)
(<foreach collection="xxx" item="item" index="index" separator="union all">
SELECT #{item.xxx}, ... FROM dual
</foreach>)
</insert>
Mysql
<insert id="xxx">
INSERT INTO xxx(...) VALUES
<foreach collection="xxx" item="emp" separator=",">
(#{emp.xxx}, ...)
</foreach>
</insert>
7.运行机制
1.读取mybatis-config.xml配置文件,根据配置文件中的属性组装Configuration对象
2.由SqlSessionFactoryBuilder对象生成SqlSessionFactory对象
3.由SqlSessionFactory对象生成SqlSession对象
4.由SqlSession对象执行SQL语句
二、提升
1.mybatis逻辑分页和物理分页
1.Mybatis自带分页RowBounds,逻辑分页
2.sql分页,limit,物理分页
逻辑分页:把所有的数据都查询出来,然后在内存中对集合进行分页,内存开销比较大,在数据量比较小的情况下效率高
物理分页:内存开销比较小,在数据量很大的情况下,建议使用物理分页
2.一级缓存和二级缓存
一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存的属性类需要实现Serializable序列化接口,可在它的映射文件中配置<cache/> ;
对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有select中的缓存将被clear。
3.延迟加载
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询,在Mybatis配置文件中,可以配置是否启用延迟加载,lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。
4.事务
使用@EnableTransactionManagement(SpringBoot启动类)和@Transactional开启事务,
@Transactional如果加在类上,那该类所有的方法都会被事务管理,如果加在方法上,那仅仅该方法符合具体的事务,一般都是加在方法上,只有增、删、改才会需要事务, 默认RuntimeException类型的异常才会回滚,可以通过@Transactional(rollbackFor = Exception.class)使所有的异常都能回滚。
RabbitMQ
AMQP是一个提供统一消息服务的应用层标准高级消息队列协议,约定了消息的格式和工作方式,消息发送与接受的双方遵守这个协议可以实现异步通讯。
RabbitMQ是一个实现了AMQP的消息队列服务,用Erlang语言。
使用场景:跨系统的异步通信,多个应用之间的松耦合的接口,应用内的同步变异步(比如订单处理),消息驱动的架构,发布订阅,跨局域网甚至跨城市的通讯。
1.角色
1.生产者:消息的创建者,负责创建和推送数据到消息服务器;
2.消费者:消息的接收方,用于处理数据和确认消息;
3.代理:就是RabbitMQ本身,扮演快递的角色。
2.组件
1.ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用;
2.Channel(信道):消息推送使用的通道,建立在TCP连接上;
3.Exchange(交换器):用于接收、分配消息;
4.Queue(队列):用于存储生产者的消息;
5.RoutingKey(路由键):用于把生产者的数据分配到交换器上;
6.BindingKey(绑定键):用于把交换器的消息绑定到队列上。
3.vhost
vhost可以理解为虚拟broker,即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等,但最最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制,vhost可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的vhost中)。
4.消息发送流程
首先客户端必须连接到RabbitMQ服务器才能发布和消费消息,客户端和rabbit server之间会创建一个tcp连接,一旦tcp打开并通过了认证(用户名和密码),客户端和RabbitMQ就创建了一条amqp信道(channel),信道是创建在tcp上的虚拟连接,amqp命令都是通过信道发送出去的,每个信道都会有一个唯一的id,发布消息、订阅队列都是通过这个信道完成的。
5.消息可靠性
1.消息持久化:磁盘;内存(消息都存储在内存中,重启服务器消息丢失,性能高于磁盘);
2.消费者ACK确认机制;
3.生产者消息确认机制:事务和Confirm发送方确认模式,都是Channel调用。
6.广播类型
1.fanout:所有bind到此exchange的queue都可以接收消息(纯广播,绑定到RabbitMQ的接受者都能收到消息);
direct:通过routingKey和exchange决定的那个唯一的queue可以接收消息,默认;
topic:所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息。
7.延迟消息队列
1.消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
2.使用RabbitMQ-delayed-message-exchange插件实现延迟功能。
8.集群
高可用:某个服务器出现问题,整个RabbitMQ还可以继续使用;
高容量:集群可以承载更多的消息量。
各节点之间使用“--link”连接,此属性不能忽略;各节点使用的erlang cookie值必须相同,此值相当于秘钥,用于各节点的认证;整个集群中必须包含一个磁盘节点。
每个节点是其他节点的完整拷贝吗:不是。存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据;性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。
唯一一个磁盘节点崩溃了会发生什么情况:集群可以继续路由消息,但是不可以做以下操作:创建队列,创建交换器,创建绑定,添加用户,更改权限,添加和删除集群节点,因为上述操作都需要持久化到磁盘节点上,以便内存节点恢复故障可以从磁盘节点上恢复元数据。
集群节点停止顺序:先关闭内存节点,再关闭磁盘节点。
9.编程
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
@Configuration
public class RabbitmqConfig {
@Bean
public Queue immediateQueue() {
//queue的名字,是否支持持久化
return new Queue("immediate_queue_test1", true);
}
@Bean
public DirectExchange immediateExchange() {
//一共有三种构造方法,可以只传exchange的名字,
//第二种,exchange名字,是否支持持久化,是否可以自动删除,
//第三种在第二种参数上可以增加Map,Map中可以存放自定义exchange中的参数
return new DirectExchange("immediate_exchange_test1", true, false);
}
@Bean
//把队列和exchange绑定在一起
public Binding immediateBinding() {
return BindingBuilder.bind(immediateQueue()).to(immediateExchange()).with("immediate_routing_key_test1");
}
}
生产者
@Component
public class Sender {
@Autowired
RabbitTemplate rabbitTemplate;
public void send() {
String message = "message" + new Date();
rabbitTemplate.convertAndSend("immediate_exchange_test1", "immediate_routing_key_test1", message);
}
}
消费者
@Component
public class Receiver {
@RabbitHandler
@RabbitListener(queues = "immediate_queue_test1")
public void immediateProcess(String message) {
System.out.println("Receiver" + message);
}
}
JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
一、组成
JVM主要由类加载器,运行时数据区,执行引擎以及本地库接口组成;
类加载器会把Java代码转换成字节码,运行时数据区再把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器--执行引擎,将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其他语言的本地库接口来实现整个程序的功能。
二、JVM的生命周期:
1.JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
2.JVM实例的运行
main()作为该程序初始线程的起点,任何其他线程均由该线程启动,JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
3.JVM实例的消亡
当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类或者java.lang.System.exit()来退出。
三、类加载机制
类的加载过程是指将java编译之后的class文件读入到内存中,然后在堆区创建一个java.lang.Class对象,用于封装类在方法区内的数据结构,类加载的最终目的是封装类在方法区的数据结构,并向java程序员提供访问方法区数据的接口。
1.类的生命周期
加载、连接、初始化、使用、卸载。
1.加载:类的加载过程主要完成3件事,a.通过类的全限定名来获取定义此类的二进制字节流,b.将这个类字节流代表的静态存储结构转为方法区的运行时数据结构,c.在堆中生成一个代表此类的java.lang.Class对象,作为方法区这些数据的访问入口。这个过程主要是类加载器完成的。
2.连接:这个过程分3个阶段(校验,准备,解析)。a.校验,校验class文件包含的信息是否符合jvm的规范,通过对文件格式,元数据,字节码,符号引用验证来完成;b.准备,为类变量分配内存,并将其初始化为默认值;c.解析,把类型中的符号引用转换成为直接引用,具体的解析有4种,类或接口的解析,字段解析,类方法解析,接口方法解析。
3.初始化:即执行类的构造方法的过程,有5种方法可以完成初始化:a.调用new方法,b.使用Class类的newInstance方法(反射机制),c.使用Constructor类的newInstance方法(反射机制),d.使用Clone方法创建对象,e.使用(反)序列化机制创建对象。
4.使用:完成类的初始化后,就可以对类进行实例化,在程序中进行使用了。
5.卸载:当类被加载,连接和初始化后,它的生命周期就开始了,当代表类的class对象不再被引用时,class对象就会结束生命周期,类在方法区内的数据就会被卸载,因此一个类何时结束生命,取决于代表它的class对象何时结束生命。
2.双亲委派模型
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在JVM中的唯一性,每一个类加载器,都有一个独立的类名称空间,类加载器就是根据指定全限定名称将class文件加载到JVM内存,然后再转化为class对象。
1.类加载器分类:
启动类加载器,是虚拟机自身的一部分,用来加载<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中并且被虚拟机识别的类库;
扩展类加载器:负责加载<JAVA_HOME>\lib\ext目录下或Java.ext.dirs系统变量指定的路径中的所有类库;
应用程序类加载器,负责加载用户类路径上的指定类库,开发者可直接使用。
2.双亲委派模型:如果一个类加载器收到了类加载的请求,它首先把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object的类,并放在程序的Classpath中,那系统中将会出现多个不同的Object类,java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
四、运行时数据区(内存)
Java程序在运行时,需要在内存中的分配空间,为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式,具体划分为5个内存空间:虚拟机栈、堆、方法区、程序计数器、本地方法栈。
1.堆
存放所有new出来的东西,对象实例以及数组(数组引用是存放在Java栈中的),堆是被所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,堆是Java垃圾收集器管理的主要区域,Java的垃圾回收机制会自动进行处理。
2.虚拟机栈(JVM栈、Java栈)
线程私有,每个线程创建的同时都会创建自己的JVM栈,互不干扰;
Java栈是Java方法执行的内存模型,Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属类的运行时常量池的引用、方法返回地址和一些额外的附加信息;
当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈,当方法执行完毕之后,便会将栈帧出栈。
3.程序计数器
在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相干扰,否则就会影响到程序的正常执行次序,程序计数器是每个线程私有的。
4.本地方法栈
JVM采用本地方法堆栈来支持本地方法的执行,此区域用于存储每个本地方法调用的状态。
5.方法区
在方法区中,存储了每个类的信息(包括类的名称、修饰符、方法信息、字段信息),当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区,方法区是全局共享的;
在方法区中有一个非常重要的部分就是运行时常量池,用于存放静态编译产生的字面量(字符串,基本类型的常量等)和符号引用,运行时生成的常量也会存在这个常量池中,比如String的intern方法,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。
6.堆、栈、队列
1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
4.队列和栈都是被用来预存储数据的;
5.队列允许先进先出检索元素,但也有例外的情况,Deque接口允许从两端检索元素;
6.栈后进先出。
五、垃圾回收器
1.怎么判断对象是否可以被回收
堆和方法区需要垃圾收集器关注
1.引用计数器:为每个对象创建一个引用计数,有对象引用时计数器+1,引用被释放时计数-1,当计数器为0时就可以被回收,不能解决循环引用的问题;
2.可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是可以被回收的。
2.算法
1.标记-清除算法
分为标记阶段和清除阶段,标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。效率不高,有内存碎片。
2.复制算法
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。耗费内存。
3.标记-整理算法
完成标记之后,将存活对象都向一端移动,然后清理掉端边界以外的内存。
4.分代算法
分代收集算法是目前大部分JVM垃圾收集器采用的算法。核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域,一般情况下将堆区划分为年轻代(1/3)和老年代(2/3);在堆区之外还有一个永久代(方法区);
年轻代的特点是每次垃圾回收时都有大量的对象需要被回收,使用复制算法;3个分区:Eden、To Survivor、From Survivor,把Eden和From Survivor存活的对象放入To Survivor区;清空Eden和From Survivor分区;From Survivor和To Survivor分区交换,每次在From Survivor到To Survivor移动时年龄就+1,当年龄到达15(默认配置)时,升级为老年代;
老年代的特点是每次回收都只回收少量对象,使用标记-整理算法;
永久代用来存储类、常量、方法描述等,对永久代主要回收废弃常量和无用的类。
3.垃圾回收器
Serial收集器,ParNew收集器,Paralle收集器,Paralle Old收集器,Cms收集器,G1收集器
1.Serial收集器
复制算法,单线程收集器,暂停所有用户线程,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。
2.ParNew收集器
复制算法,是Serial收集器的多线程版本,暂停所有用户线程,使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
3.CMS收集器
标记-清除算法,高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu追求高响应的选择,非常适用B/S系统,使用-XX:+UseConcMarkSweepGC来指定使用CMS。
4.G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境,是一款并行与并发收集器,与用户线程并发执行,并且它能建立可预测的停顿时间模型,是JDK 9以后的默认GC选项。
六、JVM调优
1.图形化工具
jconsole,用于对JVM中的内存、线程和类等进行监控。
jvisualvm,可以分析内存快照、线程快照、程序死锁、监控内存的变化、gc变化等。
2.命令行工具
java进程pid
jps [options] [pid]
jinfo [option] <pid>查看JVM参数和动态修改部分JVM参数;
jstat [option] LVMID [interval] [count]查看JVM运行时的状态信息,包括内存状态、垃圾回收等。LVMID是进程id,interval是打印间隔时间(毫秒),count是打印次数(默认一直打印);
jstack是用来查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合,使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等;
jmap生成堆dump文件和查看堆相关的各类信息,例如查看finalize执行队列,heap的详细信息和使用情况;
jhat分析jmap生成dump文件,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上。
3.参数
-Xms2g:初始化堆大小为2g;
-Xmx2g:堆最大内存为2g;
-XX:NewRatio=4:设置年轻代和老年代的内存比例为1:4;
-XX:SurvivorRatio=8:设置新生代Eden和Survivor比例
-XX:+PrintGC:开启打印gc信息;
-XX:+PrintGCDetails:打印gc详细信息。
网友评论