方法区中的符号引用和直接引用
方法区是Java虚拟机的一部分,主要用于存储类级别的信息,如类的结构、常量池、静态变量以及编译后的代码等。在方法区中,符号引用与直接引用是关联类中方法和字段调用的两个重要概念。
符号引用:
-
定义:符号引用包含一系列符号,用于描述类文件结构中的元素,它们是间接、抽象的引用形式,通常是一些文本字符串。
-
内容:符号引用通常包含如下信息:
-
类和接口的全限定名
-
字段的名称和描述符
-
方法的名称和描述符
-
方法句柄和方法类型
-
-
作用:符号引用在类文件中作为常量信息存在,它们在编译时创建,运行时需要通过解析转换成直接引用才能被JVM使用。
直接引用:
-
定义:直接引用是具体化的引用,它直接指向目标数据或者方法代码的内存地址或偏移量。
-
内容:直接引用的实际内容取决于它所指向的对象的种类,可能是以下几种:
-
对于类静态属性,可能是一个静态存储区域的地址。
-
对于实例字段,可能是相对于对象头部的偏移量。
-
对于方法,可能是指向方法代码的指针或偏移量。
-
-
作用:直接引用使得JVM可以高效地执行方法调用和字段访问操作,因为它们提供了一条直接通向目标数据或方法代码的路径。
举例说明:
比方说我们有一个Java类ExampleClass,它有一个实例字段counter和一个方法increment():
package com.example;
public class ExampleClass {
public int counter = 0;
public void increment() {
counter++;
}
}
当这个类被编译后,在.class文件中,对于counter字段和increment方法的引用会以符号引用的形式存在。它们可能会呈现如下:
-
符号引用到counter字段可能是:“com/example/ExampleClass.counter:I”
-
符号引用到increment方法可能是:“com/example/ExampleClass.increment:()V”
当这个.class文件被加载到JVM中,并且类ExampleClass被第一次使用时(例如,创建ExampleClass的实例或者调用它的方法),JVM会把这些符号引用解析成直接引用。具体来说:
-
对于字段counter,直接引用将包括该字段在ExampleClass对象中存储的具体内存位置的偏移量。
-
对于increment方法,直接引用将是指向JVM中方法区内该方法代码开始位置的指针。
这个过程确保了运行时的效率,因为解析工作只需完成一次,之后JVM就可以直接利用这些引用,无需再进行查找和匹配,从而加速程序执行。
什么是 SpringCloud?
Spring Cloud 是一系列框架的集合,旨在为分布式系统的开发提供解决方案。它基于 Spring Boot,利用 Spring Boot 的快速开发特性,帮助开发者快速构建分布式微服务架构系统。Spring Cloud 通过集成和封装各种成熟的开源解决方案,提供了配置管理、服务发现、负载均衡、断路器、分布式追踪、消息驱动微服务、网关路由等功能。
了解了 SpringCloud 提供了什么能力:
1)配置管理:集中管理配置,支持动态刷新。
2)服务发现和注册:实现服务的自动注册和发现。
3)负载均衡:提供客户端负载均衡解决方案。
4)断路器:实现故障隔离和容错机制。
5)分布式追踪:跟踪微服务调用链路,便于监控和调试。
6)消息驱动的微服务:通过消息中间件实现服务间的异步通信。
7)网关路由:提供API网关,进行路由和过滤。
Spring Cloud 组件代码片段
Spring Cloud Config
Spring Cloud Config(配置管理) 提供了一种集中管理分布式系统配置的方法。配置服务器(Config Server)可以从 Git、SVN 等版本控制系统中读取配置文件,并将其提供给各个微服务。通过这种方式,可以实现配置的集中管理和动态刷新。
// 示例:Config Server的配置
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Eureka
Eureka 是一个服务注册与发现组件,允许微服务在启动时自动注册到 Eureka Server,并在需要调用其他服务时通过 Eureka Client 进行查找和调用。
示例:Eureka Server 的配置
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Ribbon
Ribbon 是一个客户端负载均衡器,它能够对多实例的微服务进行负载均衡,提升服务调用的可靠性和性能。Ribbon 可以与 Eureka 集成,实现动态的服务实例选择。
示例:使用 Ribbon 进行负载均衡
@RestController
public class RibbonController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consume")
public String consume() {
return restTemplate.getForObject("http://SERVICE-NAME/endpoint", String.class);
}
}
Feign
Feign 是一个声明式的 HTTP 客户端,它简化了 HTTP API 的调用。通过注解方式定义 HTTP 请求,可以像调用本地方法一样调用远程服务。
// 示例:Feign 客户端的定义
@FeignClient(name = "service-name")
public interface ServiceClient {
@GetMapping("/endpoint")
String callEndpoint();
}
Spring Cloud Gateway
Spring Cloud Gateway 是一个现代化的 API 网关,它提供了动态路由、限流、断路、重试等功能,能够处理所有外部请求的路由和过滤。相比于 Zuul,Spring Cloud Gateway 具有更高的性能和更丰富的功能。
// 示例:Gateway 的配置
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://http.org"))
.build();
}
}
实际事物加深记忆的例子
想象下,经营着一家大型连锁超市,各个分店相当于微服务,每个分店需要定期向总部(配置服务器)获取商品价格(配置)和库存信息(配置)。每个分店的员工(服务实例)在接待顾客(请求)时,需要查找商品(服务发现),确保顾客可以在多个分店中进行购物(负载均衡)。当顾客咨询商品信息时,可以通过电话直接联系其他分店的员工(Feign),而总服务台(API 网关)则负责处理所有顾客的咨询电话,确保顾客的请求能够快速准确地转接到相应的分店。
通过这个例子,我们可以更好地理解 Spring Cloud 各个核心组件的作用及其在实际应用中的意义。Spring Cloud 为我们提供了构建和管理分布式系统的强大工具,使得开发和运维微服务架构变得更加简便和高效。
其他补充
Spring Cloud的核心组件及其功能补充:
1)Spring Cloud Netflix Zuul:
-
功能:API网关服务。
-
用途:提供动态路由、监控、弹性、安全等功能,处理所有外部请求的路由。
2)Spring Cloud Netflix Hystrix:
-
功能:实现熔断器模式。
-
用途:提供容错和延迟容忍能力,防止单个服务的失败导致整个系统崩溃。
3)Spring Cloud Sleuth:
-
功能:分布式追踪工具。
-
用途:为Spring Cloud应用添加分布式追踪功能,与Zipkin或Jaeger集成,用于跟踪请求在微服务中的调用链路。
4)Spring Cloud Bus:
-
功能:事件总线,用于传播集群中的状态变化。
-
用途:通过消息代理(如RabbitMQ、Kafka)在多个服务实例之间传播配置更新或其他事件。
5)Spring Cloud Stream:
-
功能:消息驱动微服务框架。
-
用途:简化与消息中间件的整合,提供统一的编程模型,支持Kafka、RabbitMQ等。
localhost和127.0.0.1啥区别?
在日常开发和网页浏览中,我们经常会遇到localhost
和127.0.0.1
。它们看似相似,实则有着本质的区别。
本地主机(localhost)
localhost
是一个特殊的域名,它代表的是本机地址。它的存在主要是为了方便开发者和用户记忆,因为相比于IP地址,域名更容易被记住和识别。
localhost
的有效范围仅限于本机。这意味着在同一局域网内的其他计算机上访问localhost
,它们将无法找到目标主机,因为localhost
并不指向一个网络中的真实地址,而是指向本机。这在开发环境中非常有用,因为它允许开发者在不暴露内部开发服务器的情况下进行本地测试。
本地回环地址(127.0.0.1)
与localhost
相对的是127.0.0.1
,这是一个特殊的IP地址,被称为本地回环地址。它同样用于指向本机,但是它是一个IP地址,这意味着它遵循IP地址的规则和结构。
127.0.0.1
是TCP/IP协议中定义的官方IPv4地址,用于在本地计算机上测试网络服务。由于它是一个IP地址,它可以直接与网络栈交互,而不需要DNS解析。这使得使用127.0.0.1
进行网络通信更为直接和快速。
域名解析
localhost
和127.0.0.1
之间的联系在于它们都指向同一个地方—本机。但是,它们的解析方式不同。localhost
是通过DNS解析实现的,而127.0.0.1
则不需要DNS解析。
在每台计算机上,都有一个名为hosts
的文件,它包含了一些本地DNS解析规则。在大多数操作系统中,hosts
文件会有一个预定义的条目,将localhost
映射到127.0.0.1
。这意味着当你在浏览器中输入http://localhost
时,操作系统会查找hosts
文件,将localhost
解析为127.0.0.1
,然后浏览器会向这个IP地址发送请求。
应用场景
在实际应用中,localhost
和127.0.0.1
有着不同的用途。
-
• 开发和测试:开发者在进行本地开发和测试时,通常会使用
localhost
作为开发服务器的地址。这样做的好处是,即使在不同的开发环境中,只要使用localhost
,就可以确保指向的是本机的开发服务器。 -
• 网络服务:在需要确保网络请求不会离开本机的情况下,使用
127.0.0.1
是一个更安全的选择。例如,在进行网络安全测试或者需要隔离网络请求时,127.0.0.1
可以确保所有的网络交互都局限在本机。
域名的等级划分
域名系统是分层的,从顶级域名(TLD)到二级域名(SLD),再到三级域名(3LD)。
顶级域名(TLD):顶级域名是域名系统中最高级别的域名。它位于域名的最右边,通常由几个字母组成。顶级域名分为两种类型:通用顶级域名和国家顶级域名。常见的通用顶级域名包括表示工商企业的.com、表示网络提供商的.net、表示非盈利组织的.org等,而国家顶级域名则代表特定的国家或地区,如.cn代表中国、.uk代表英国等。
二级域名(SLD):二级域名是在顶级域名之下的一级域名。它是由注册人自行选择和注册的,可以是个性化的、易于记忆的名称。例如,juejin.cn 就是二级域名。我们平常能够申请到的也是这种。目前来说申请 xxx.com、xxx.net、xxx.cn等等域名,其实大家不太关心其顶级域名com\net\cn代表的含义,看着简短好记是主要诉求。
三级域名(3LD):三级域名是在二级域名之下的一级域名。它通常用于指向特定的服务器或子网。例如,在blog.example.com中,blog就是三级域名。www是最常见的三级域名,用于代表网站的主页或主站点,不过这只是某种流行习惯,目前很多网站都推荐直接使用二级域名访问了。
localhost
在这个分层结构中是一个特例。虽然它看起来像一个顶级域名,但它实际上是一个保留的顶级域,专门用于访问当前计算机。
对称加密和非对称加密在HTTPS中的应用
对称加密(Symmetric Encryption)
对称加密使用相同的密钥进行数据的加密和解密。发送方将明文数据通过对称加密算法和密钥进行加密,接收方收到密文后,使用同样的密钥和对称加密算法进行解密以恢复明文。
优点:
-
加密和解密速度快
-
适用于大量数据的加密
缺点:
-
密钥分发问题,双方都需要有同样的密钥,安全密钥交换成为挑战
-
密钥管理成本高,每个通信对需要一个独立的密钥
常见算法:AES, DES, 3DES, RC4等。
非对称加密(Asymmetric Encryption)
非对称加密使用一对密钥:公钥(public key)和私钥(private key)。公钥可以公开,而私钥必须保密。如果用公钥加密数据,只有对应的私钥才能解密;如果用私钥加密数据,只有对应的公钥才能解密。
优点:
-
解决了密钥分发的问题,密钥对中的公钥可以自由分发
-
提供了数字签名功能,确保了消息的完整性和身份验证
缺点:
-
加密和解密速度慢于对称加密,不适合大量数据加密
-
计算复杂度高
常见算法:RSA, ECC, ElGamal等。
HTTPS中的应用:
HTTPS(超文本传输安全协议)是一种用于在互联网上安全传输数据的协议。它在标准的HTTP(超文本传输协议)上增加了SSL/TLS(安全套接层/传输层安全)协议。SSL/TLS结合了对称加密和非对称加密,兼顾了两者的优势。
-
建立连接时的非对称加密:
-
浏览器向服务器请求建立SSL连接。
-
服务器发送其SSL证书,包括服务器的公钥。
-
浏览器验证证书的有效性,然后生成一个随机数作为对称密钥的会话密钥。
-
浏览器用服务器的公钥加密这个会话密钥,并发送给服务器。
-
服务器用自己的私钥解密这个会话密钥。
-
-
数据传输时的对称加密:
-
一旦双方都有了会话密钥,它们就使用对称加密算法加密通信过程中的所有数据。
-
这样,即使有人截获了加密后的数据,也无法解密,因为他们没有会话密钥。
-
-
身份验证和完整性检查:
-
服务器和客户端还可能使用非对称加密提供数字签名,以确保数据的完整性和来源验证。
-
总结:HTTPS协议通过结合对称和非对称加密的优点,提供了既安全又高效的网络数据传输方法。在建立连接阶段使用非对称加密来交换密钥,保证了密钥的隐私和安全;而在数据传输阶段使用对称加密来保障数据传输的效率。此外,通过数字签名保障了数据的完整性和来源的可验证性。这种混合加密策略兼顾了性能和安全性。
自定义工厂类给线程池命名
项目模块较多,在运行时调用了不同模块的线程池,为了在发生异常后快速定位问题,一般会在构建线程池时给它一个名字。
方法一: 通过Spring 框架提供的CustomizableThreadFactory命名
ThreadFactory springThreadFactory = new CustomizableThreadFactory("Spring线程池:");
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),springThreadFactory);
exec.submit(() -> {
log.info(exec.toString());
});
方法二: 通过Google guava工具类提供的ThreadFactoryBuilder命名
//链式调用
ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("guava线程池:").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),guavaThreadFactory );
exec.submit(() -> {
log.info(exec.toString());
});
其实还有一个是Apache commons-lang3 提供的 BasicThreadFactory工厂类,也可以给线程池命名,他们的本质都是通过Thread 的setName()方法实现的。
方法三: 自定义工厂类实现线程池命名
先定义一个工厂类,通过实现ThreadFactory的newThread方法,完成命名。
public class MyThreadFactory implements ThreadFactory {
private final AtomicInteger threadNum = new AtomicInteger();
private final String name;
/**
* 创建一个带名字的线程池生产工厂
*/
public MyThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName(name + "-" + threadNum.incrementAndGet());
return t;
}
}
调用一下看看结果:
@Slf4j
public class Test {
public static void main(String[] args) {
MyThreadFactory myThreadFactory = new MyThreadFactory("javaBuild-pool");
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(10),myThreadFactory);
exec.submit(() -> {
log.info(exec.toString());
});
}
}
进程和线程的区别?
进程和线程是操作系统中用于并发编程的两种基本单位,它们在资源管理、执行流程等方面有着本质的区别。
进程(Process):
-
定义:进程是操作系统进行资源分配和调度的独立单位,是应用程序运行的载体。一个进程通常包含代码、数据和分配给它的系统资源。
-
资源隔离:每个进程拥有独立的地址空间,一个进程崩溃不会直接影响到其他进程。
-
通信成本:进程间通信(IPC)需要特殊的操作系统支持,如管道、消息队列、共享内存等,相比线程间通信较为复杂且效率较低。
-
切换开销:进程切换涉及当前执行状态的保存以及新进程状态的装载,上下文切换开销比线程大。
优点:
-
稳定性高:进程之间相互独立,崩溃的进程不会影响到其他进程。
-
安全性好:独立的地址空间加强了数据安全。
缺点:
-
开销大:创建进程比创建线程资源消耗大,上下文切换时间长。
-
通信复杂:进程间通信机制相对麻烦,效率不如线程间通信。
线程(Thread):
-
定义:线程是进程内的一个独立执行流,是被系统独立调度和分派的基本单位。一个进程可以包含多个线程,它们共享进程的地址空间和资源。
-
资源共享:同一进程内的线程共享内存和文件等资源,这使得线程间的通信非常方便。
-
通信成本:线程间的通信可以通过直接读写进程内的数据进行,开销小。
-
切换开销:线程切换只需少量寄存器和栈的变更,因此速度快。
优点:
-
创建快速:创建线程比进程快,资源消耗小。
-
切换快速:线程间切换的开销小于进程切换。
-
通信高效:线程间可以直接通信,共享内存,无需IPC。
缺点:
-
稳定性低:一个线程崩溃可能会影响整个进程。
-
安全性差:线程间共享资源,数据处理不当容易导致冲突。
综合来说,进程相比线程更为稳定安全,但代价是更高的资源消耗和通信成本;而线程则灵活高效,但协作中的风险也更大。选择使用进程或线程通常取决于具体情况:如果需要执行多个相对独立且需要隔离的任务,适合使用多进程;如果任务之间需要频繁交换信息,又或者是在同一应用内的多个执行任务,使用多线程则更加合适。