说之前先捋清一个大致的思路:创建对象的过程大致分为 5 步:
当我们在 Java 程序中 new 一个对象的时候,在底层其实会有大概以下几步:
检查完类加载后就是分配内存了。(这里有人可能会问那该对象的具体内存是否确认呢?其实类加载完成后可以确认它所需要的内存了)。
现在我们已经知道了对象所占的内存,那么虚拟机是如何给对象在 Java 堆中分配内存的呢?主要有两种分配方式:
接下来我们详细说说这两种分配内存的方式:
指针碰撞
其实这种方式理解起来比较简单的,假设 Java 堆中的内存是绝对完整的,它会把使用过的内存和未使用过的内存划分开来。此时一边就是使用过的内存,一边就是未使用过的内存;那么他如何去给一个新的对象去划分空闲内存中的某块区域呢?其实很简单,就是借助一个指针(这里是不是呼应上了所谓的指针碰撞);
当我们分配内存的时候就是把指针在空闲的内存区域中移动一个与要被创建对象大小相等的距离。这就是指针碰撞的方式。
适用场景:内存规整,不碎片化
这个其实理解起来更为简单。它无非就是指在 Java 堆中的内存并非是规整的(使用的内存和未使用过的内存没有划分开来),比较杂乱无章,此时虚拟机就得需要列表记录内存中哪些是已经使用的哪些是没有使用的,然后在给对象分配内存空间的时候在该列表中找一个足够的内存分给对象实例;并更新维护的列表。这种就叫做空闲列表(Free List)
。
适用场景:堆内存碎片化
Tip:说到分配内存的两种方式,就顺便提一句,
Serial、ParNew
等压缩整理过程的收集器的时候,系统采用的是指针碰撞的方式。CMS
这种基于清除的算法收集器,理论上就只能采用空闲列表。上面我们将给新的对象分配内存的方式以及分配内存前的逻辑大致理完了。你是不是觉得很简单。其实就是这么简单。但是其实我们忽略了一个很重要的问题。我们回想起本篇文中第一段话:Java 程序在运行过程中无时无刻不在创建对象,那么它是如何在并发环境下保证线程安全的呢?接下来我们简单的捋一下其实保证线程安全还是两种方式:
CAS
+ 失败重试
)来保证分配内存空间的原子性;本地线程分配缓冲 Thread Local Allocation Buffer TLAB
,当本地线程缓冲使用完了,再分配缓存区时才需要同步锁定。至于虚拟机是否使用 TLAB 可通过参数-XX: +/-UseTLAB
来控制。当分配完内存后,虚拟机必须将分配到的内存空间(不包含对象头)都初始化为零值。如果使用了 TLAB,那么这一步会在 TLAB 分配时进行。为什么虚拟机要有这番操作呢?
主要是为了保证对象的实例字段能够在 Java 代码中可以在不赋值的是否就可以访问直接使用,这样就能使 Java 程序访问这些字段所对应的数据类型的初始零值
接下来,Java 虚拟机还需要对这些对象进行必要的设置,例如这些对象是哪些类的实例、以及如何才能找到类的元信息、对象的哈希码(实际对象的哈希码会延期到真正调用 Object::hashCode()方法时才计算)、对象 GC 的分代年龄等信息,这些信息都会保存在对象头中(Object Header)之中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式
执行完上述操作后,对于 Java 虚拟机来说对象已经创建完了,但是对于 Java 视角来说,对象的创建才刚刚开始,还没有执行init
方法。所有的字段还都为零。对象中需要的其它资源和状态信息还没有按照原有的意图去构造好。所以一般来说,new指令
之后就会执行init
方法,按照 Java 程序员的意图去对对象做一个初始化,这样之后一个真正完整可用的对象才构造出来
虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前):
双亲委派机制是 Java 类加载器的一种设计模式,其核心思想是每个类加载器在加载类时首先将请求委派给父类加载器,只有在父类加载器无法完成加载时才由当前类加载器自己加载。这一机制的目的是确保 Java 类库的一致性,防止不同的类加载器重复加载相同的类,从而保证 Java 应用程序的稳定性和安全性。
如果不按照双亲委派机制进行类加载,可能会导致以下问题:
线程是程序执行的单元,它包含了一些状态信息,线程的状态是线程在执行过程中不同阶段的表现。在Java中,线程的状态有几种,主要包括以下几种:
线程对象包含了一些信息,这些信息主要有:
这些信息共同组成了线程的上下文,用于保存和恢复线程的执行状态。线程的状态会随着线程的执行过程而不断变化。在多线程编程中,了解线程状态和线程的上下文是非常重要的,可以帮助开发人员调试和优化多线程程序。
线程池是一种管理和复用线程的机制,它可以有效地控制并发线程的数量,提高系统性能。线程池的执行任务过程通常包括以下几个步骤:
Runnable
接口的实例,也可以是 Callable
接口的实例。
ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(new MyTask());以上是线程池执行任务的基本过程。不同类型的线程池(如FixedThreadPool
、CachedThreadPool
等)有不同的管理策略,但执行任务的基本原理是相似的。线程池的使用可以提高程序的性能,减少因线程的创建和销毁而带来的开销。
线程同步是为了确保多个线程在访问共享资源时能够安全地进行操作,防止数据不一致和并发问题。Java 中有多种线程同步的策略和类,其中最常见的包括:
synchronized
是 Java 内置的关键字,用于保护代码块或方法,确保同一时间只有一个线程能够访问被 synchronized
修饰的代码。使用 synchronized
可以确保线程安全,但过多的锁竞争可能导致性能问题。
public synchronized void synchronizedMethod() { // 同步的代码块 }ReentrantLock
是 java.util.concurrent.locks
包下的类,提供了显式的锁机制。相对于 synchronized
,ReentrantLock
提供了更多的灵活性,例如可以实现公平锁和可中断锁。
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 同步的代码块 } finally { lock.unlock(); }ReadWriteLock
是用于支持读写分离的锁,包括 ReentrantReadWriteLock
实现。在读操作较多的情况下,使用读写锁可以提高并发性。
ReadWriteLock lock = new ReentrantReadWriteLock(); lock.readLock().lock(); try { // 读操作的同步代码块 } finally { lock.readLock().unlock(); } lock.writeLock().lock(); try { // 写操作的同步代码块 } finally { lock.writeLock().unlock(); }java.util.concurrent.atomic
包提供了一系列的原子类,如 AtomicInteger
、AtomicLong
等。这些类提供了一些原子性操作,可以用来替代传统的锁机制。
AtomicInteger counter = new AtomicInteger(); // 原子性的自增操作 counter.incrementAndGet();关于性能方面,一般而言,synchronized
关键字的性能已经在 JDK 的不断优化中有所提升,而且在某些场景下可能比较适用。ReentrantLock
提供了更多的控制和可定制性,但可能会稍微增加一些复杂性。在具体场景中,性能的优劣很大程度上取决于实际使用情况和并发压力。
在实际应用中,性能的评估通常需要基于具体的场景和需求进行测试和比较。不同的同步机制在不同的应用场景下可能表现出不同的优势和劣势。
Spring Boot 是一个用于简化 Spring 应用开发的框架,特别适用于构建基于 RESTful 架构的 Web 服务。下面是 Spring Boot 搭建的 Web 服务的处理过程:
src/main/java
目录下创建相应的包和类。@RestController
注解将一个类标记为控制器。
@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{username}") public ResponseEntity<User> getUserByUsername(@PathVariable String username) { Optional<User> user = userService.findUserByUsername(username); return user.map(value -> ResponseEntity.ok().body(value)) .orElseGet(() -> ResponseEntity.notFound().build()); } }main
方法的类。通常使用 @SpringBootApplication
注解标记这个类。
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }http://localhost:8080/api/users/{username}
访问用户信息。以上是简化的 Spring Boot Web 服务搭建过程。实际开发中,可能会涉及更多的细节,如异常处理、日志记录、安全性等。
~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。