To gain a better understanding of what languages like Java buy you in terms of security and memory management, work in C. The only thing that will drive the point home any deeper will be working in assembler¹. While it's nothing like what Java provides, C++ gives you too many tools to manage the kind of complexity that causes these problems, and the temptation to use them will be too great.
Memory management in C is very labor intensive compared to Java and C++. You don't have any sort of automatic garbage collection, you don't have explicit destructors to free up resources when an object goes out of scope, so you have to manually track every memory allocation and match it up with a corresponding deallocation. Arrays don't know how big they are and there's no bounds checking, so you have to track all those buffer sizes yourself and enforce those limits manually. C doesn't provide much in the way of exception handling (signals and setjmp/longjmp
are the extent of it, and that's not very much at all), so you have to be careful about how you handle error conditions.
If you really want to appreciate what Java (or even C++) buy you, implement a C library that mimics the behavior of the Java String
class.
¹ The best of both worlds would be to work in C and have your compiler write the generated assembly to an output file and study that.